方法——慎用重载

news/2024/6/30 8:27:24

前言: 何为重载,类中两个方法同名,但是参数列表不同,称为重载。注意,是参数列表,不是返回值,实际上同名方法同参数列表,但不同返回值是不允许的。

// 重载示例,重载方法允许有不同的返回值类型
public void dosh(){
    //
}

public Long dosh(String s){
    //
}

// 重载方法允许重载的参数列表是父子类。
//如以下的重载。在实际执行的时候,会优先选择更精确的匹配,
//也就是如果参数是 Integer,就走第一个,如果参数不是Integer,才走第二个方法
public void dosh(Integer i){
    //
}


public void dosh(Object o){
    //
}
// 重载也包括参数个数不同
public void dosh(Integer i){}
public void dosh(Integer i,String s){}


// 但是,重载不能是方法参数列表相同而返回值不同,如下重载是错误的,会编译报错
public String dosh(){}
public void dosh(){}
public Integer dosh(){}

注意: 重载方法的选择是在编译时就确认而不是运行时确认。

如下实例,参数o 实际就是 String , 但在编译时,它用了 Object 来修饰,那么 jvm 就选择它执行的方法参数为 Object,而不是根据它实际运行时的 String 类型

public  class Period {

    public static void main(String[] args) {
        String s = "str";
        Object o = s;

        Period period=new Period();
        period.dosh(o);
    }


    public void dosh(String s){
        System.out.println("参数为 String");
    }

    public void dosh(Object o){
        System.out.println("参数为 Object");
    }

}    

// 方法重载的选择是会选择最精确的匹配,如上面的实例,我们的参数为 Integer,当方法重载的时候,java 会优先使用参数为 integer 的方法,也就是 dosh(Integer i). 当此方法不存在(被删除)时,也会自动选择 dosh(Object o ) ,因此,重载方法可能造成的问题就是在重构的时候,可能导致程序编译不报错,但实际执行的方法已经变了,可能造成程序在运行时才会出错,更大的麻烦可能是程序没有报错,但执行一段时间后,我们发现了数据错误,又很难找到原因

比如,程序最开始设计如下

// class A
@Autowired
private B b;
public void test(){

    // 获取参数 m
    Integer m = xxx;// 根据业务,算出一个值
    b.dosh(m);
}

-------------------------------------------------------
// class B
//情况1
public void dosh(Integer i){

}

//情况2
public void dosh(Object o){

} 

上面的程序中,情况1 和2 走向了不同的处理。 为了处理这些情况,我们提供了 B 类来辅助完成。

一段时间后,B 的维护者因为某些原因,移除了 dosh(Integer i)

这时,class A 依然可以正常编译,不会报错,但其执行路径却已经悄悄换成了情况2

// class A
@Autowired
private B b;
public void test(){

    // 获取参数 m
    Integer m = xxx;// 根据业务,算出一个值
    b.dosh(m);
}

-------------------------------------------------------
// class B
public void dosh(Object o){
}

对于 A 来说,没有发现B 相关的编译错误,也不可能天天关心上层有没有发生变化,或者说从使用者角度来看待,B 既然提供了这个方法,并且我没有编译错误,那B 就应该能正确执行

但对于 B 来说,他可能是无心的,即便他随着升级也可能提供相应的 change log, 或者其他方法的文档,但这种远远不如 直接程序报错来的实在

如果 ide 编译错误,直接提示我们这个方法不存在,那我们就可以针对性地找相应文档,检查应该如何更新。而如果没有报错,很多时候我们也就默认程序没有发生变化。

这就是重载容易造成的问题。


上面的例子还算容易处理,稍微注意观察一下就能看出来。

我们现在用的 jdk 多数是8,即便不是8,也基本上都是 jdk5及之后的了。

jdk5 有个重要的新增特性,自动装箱拆箱,也就是 包装类和基本类的转换 jdk会自动处理

比如 有个方法需要的参数是  Integer ,但我们传参时候传一个 int 是可以的。同样的,一个需要 int 参数的方法,我们传 Integer 也可以

并且,泛型也是5版本引入的,也就是,我们现在所开发的程序绝大多数都有这两个特性。

泛型 + 自动装箱拆箱 + 方法重载,这三个本来是方便开发的功能,但他们混合在一起时,却很容易造成方法执行路径不是我们预期的。很多程序员不注意 Integer 和int 的选择,而这就是重载方法选择错误的原因

典型的例子

// 这个是正常的
public void removeInvalid(List<String> list,String s){
     if(list.contains(s)){
         list.remove(s);
     }
}


// 这个方法执行的就不是预期的结果,甚至可能报错
public void removeInvalid(List<Integer> list,int x){
   if(list.contains(x)){
       list.remove(x);
   }
} 

上面例子中,两个方法逻辑是类似的,如果 list 中有指定的元素,就执行移除。

但我们需要注意 , List 中的 api 方法  

remove(E e) ,移除指定的元素,其逻辑是使用 e 与 List 中的元素进行对比,如果 == 或 equals(当然,==是 equals 的特殊情况)

remove(int index) , 移除指定下标位置的元素

在第一个例子中, E 泛型实际类型是 String, list.remove 实际执行的是 remove(E e)


而第二个例子中,java 帮我们处理了装箱拆箱的问题,但却恰恰因为这个过程是自动的,我们看不见,就出现了错误

contains方法是  contains(E e),这里用了 Integer, 所以 x 被自动装箱,也就是这一行代码的实际执行是    if(list.contains(Integer.valueOf(x))){

而到了第二行,根据精确匹配,java 选择了 List 接口的 remove(int index),所以,其实际移除的并不是想要的元素 x,而是把x当成下标,去移除指定位置的元素


如何避免因为重载导致的这种问题?

可以使用更精确的命名来处理

比如,之前的两个方法可以根据参数改为

doshString(String str)  

doshObject(Object o)

doshInteger(Integer i)

实际上,java.util.List 因为历史兼容问题,只能沿用旧的名称,导致 remove 方法出现了这种歧义,如果有更精确的名称,这样的问题也就不存在了

remove(E e) -> removeElement( E e)


remove(int index) -> removeByIndex(int index)

在本条目中,我们应该得到的是,区别“可以使用重载”与“应该使用重载”

什么时候合适用重载?什么时候不应该用重载

当重载方法背后的逻辑一致时才应该使用重载,否则建议使用更全的方法名来区分这些方法。


应该重载

// 如下,此重载仅仅是照顾入参的格式,实际的行为是一致的
// 这种重载设计在我的程序中比较常见,也就是考虑到不同平台交互的时候传输的数据格式问题
// 实际需要的是 String,但涉及到数据交互,多给个重载方法,也方便用户使用方便
public boolean dosh(String num){
    return dosh(Integer.valueOf(num));
}


public boolean dosh(Integer num){

} 

不应该重载

// 下面两个方法就不应该做成重载
// 虽然他们都是新增操作,但新增教师和学生,所做的后续处理可能完全不同,这种就更应该做成 addStudent(Student student)   addTeacher(Teacher teacher)

public boolean add(Student student){
    // 实际业务逻辑
}


public boolean add(Teacher teacher){
 // 实际业务逻辑
} 

http://lihuaxi.xjx100.cn/news/1178923.html

相关文章

【LAMP架构】

目录 一、LAMP架构1、组件作用 二、编译安装Apache httpd服务2、安装环境依赖包3、配置软件模块4、编译及安装5、优化配置文件路径&#xff0c;并把httpd服务的可执行程序文件放入路径环境变量的目录中便于系统识别6.添加httpd系统服务7.修改httpd 服务配置文件8.浏览器访问验证…

VSCODE 插件推荐

文章目录 项目管理Project Manager markdownmarkdown-pdfPaste ImageMarkdown Preview Enhanced 笔记Notes 思维导图vscode-mindmap 开发Visual Studio IntelliCode GitHub Repositories 项目管理 Project Manager 以下是项目管理器提供的一些功能&#xff1a; 将任何文件夹…

C++学习笔记3:sort和priority_queue的比较器重载

1 sort 三种方法 1. 直接重载函数 #include <vector> #include <memory> #include <vector> #include <queue> #include <iostream> #include <algorithm>using namespace std;class Node{ public:int value;Node(){value 0;};explici…

深入理解深度学习——正则化(Regularization):半监督学习

分类目录&#xff1a;《深入理解深度学习》总目录 在半监督学习的框架下&#xff0c; P ( x ) P(x) P(x)产生的未标记样本和 P ( x , y ) P(x, y) P(x,y)中的标记样本都用于估计 P ( y ∣ x ) P(y | x) P(y∣x)或者根据 x x x预测 y y y。在深度学习的背景下&#xff0c;半监督…

Pyhive——介绍使用举例

介绍 PyHive 是一个 Python 数据库连接工具和 ORM 框架&#xff0c;它提供了一个 Python 接口让用户可以连接多个不同的 Hadoop 数据存储系统&#xff0c;包括 Apache Hive, Apache Impala, Amazon Athena, Apache Spark SQL 等等。 PyHive 的目标是让 Python 开发者能够方便…

关于f-stack转发框架的几点分析思考

使用DPDK收包&#xff0c;想要用到TCP协议栈&#xff0c;可选的方案有linux原生的tun/tap口以及DPDK自带的KNI驱动&#xff0c;这两种都是通过将DPDK收到的报文注入到linux内核来使用TCP协议栈的功能&#xff0c;然后&#xff0c;用户态协议栈可以考虑开源的f-stack&#xff0c…

c# cad二次开发通过获取excel数据 在CAD绘图,将CAD属性导出到excel

c# cad二次开发通过获取excel数据 在CAD绘图&#xff0c;将CAD属性导出到excel using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using System; using System.Collections.Generic; using System.Linq; us…

你在项目中是如何使用kafka的?

消息中间件是现代分布式系统中不可或缺的组件之一&#xff0c;它提供了高可靠性、高吞吐量的消息传递机制。Kafka作为一种开源的分布式消息队列系统&#xff0c;广泛应用于各行各业。本篇博客将介绍在实践中使用Kafka的一些技巧和最佳实践&#xff0c;帮助开发人员更好地利用Ka…