深入理解Java的方法重载机制

Java的“重载机制”为开发者提供了便利的同时也规定了一系列的规范和限制,本文就是为了深入理解在“重载”表像之后隐藏的物理本质

*声明,本文的所有代码均在Java语言环境下测试

什么是方法重载:重载的概念

在搞明白“重载”之前要先弄清楚另一个概念“方法签名”
方法签名 = 方法名 + 参数列表 (方法签名不包括返回值)
我们用方法签名区分不同的方法,而所谓“重载”,指的就是“两个方法间的方法签名中方法名相同而参数列表不同的情况”,比如:

void sum(int a, int b){
    System.out.println("a + b = "+(a+b));
}
void sum(double a, double b){
    System.out.println("a + b = "+(a+b));
}

调用时如果传入的参数是sum(1, 2),就调用上面的方法;如果传入的参数是sum(1.1, 2.2),就调用下面的方法;如果传入的参数是sum(1, 3.14),还是调用下面的方法,因为要有一个信念就是编译器总是有能力找出“最适合的对应调用方法”,这里“最适合”指的是“最匹配”、“精确度最高”,值得注意的是第三个调用的第一个参数在编译时先要被转换为1.0,然后才能调用下面的方法(但是如果有方法三sum(int a, double b)那么就调用方法三)

方法重载,对于像Java、C、C++这样的静态语言来说其作用就是“最大限度的增强代码复用性,减少程序员的体力劳动”(为什么我不说Python这样的动态类型语言?因为人家的类型是不确定的呀!好吧扯远了……)

任何方法都可以“重载”的,包括“构造函数(构造方法)”

继承来的方法也可以重载吗:当然可以

对照着我们的定义再看一遍,“方法名相同而参数列表不同……”,再把继承即概念抠出来,“……子类会继承父类的的所有方法(父类对象作为子类对象的内核)”——这也就意味着,我们的子类方法可以重载继承得来的方法

这里最容易混淆的两个概念就是“重载继承来的方法”和“覆写继承来的方法”

public class Father{
    ...
    public void func(int a, int b){
        System.out.println("a + b = "+(a+b));
    }
    ...
}
public class Son extends Father{
    ...
    public void func(double a, double b){ //这叫“重载”
        System.out.println("a + b = "+(a+b));
    } //“重载”并不关心方法体或返回值是否相同
    publcic void func(int a, int b){ //这叫“覆写”
        System.out.println("a - b "+(a-b));
    } //“覆写”只能改变方法的主体部分,而返回值和方法签名必须和父类一致
    ...

窃以为这一个例子就够了,保证以后不会出错就行

当重载遇到范型

背景知识:“范型”是JDK1.5引入的一个很棒的特性
在有范型之前是这样的:

ArrayList al = new ArrayList();
al.add("Hello");
al.add(1.5);
String element1 = (String)al.get(0); /*得到的是Object类型
int element2=(Integer)al.get(1);     必须强制类型转换*/

自从有了范型之后:

ArrayList<String> al = new ArrayList<>();
al.add("Hello");
al.add("1.5"); //1.5的话编译器报错!
String element1 = al.get(0);
String element2 = al.get(1);

范型的引入,实际上是在编译期增加了一次额外的类型检查以防止体制外的类型的介入,保证了类型安全

关于范型只要注意一下几点就可以无敌了:
1. 范型分为范型类和范型方法,确定范型种类的是所谓的“类型参数”
2. 范型的“类型参数”中只可以传递指向实际对象的变量,不允许填入字面量
3. 范型的工作原理是“类型擦除”后再“类型填充”
4. 范型的存活只限于“编译期”
5. Java不支持“范型数组”
其中第三、四点,具体来说:“Java语言的泛型基本上完全是在编译器中实现的,由编译器执行类型检测和类型推断,然后生成普通的非泛型的字节码;JVM是完全意识不到范型的存在的,这称为类型擦除;编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除”

好啦!背景知识介绍完了,下面进入正题——当重载遇到范型,会碰撞出怎样的火花?话不多少,直接上代码:

public static<N extends Number> double sum(N a, N b){
    double sum = a.doubleValue() + b.doubleValue();
    return sum;
}

范型原来还可以这么玩——只用一个方法就表示若干个本来需要重载的方法

然,正如古训说的:“世间万物都有两面性”——范型也不是个例外:

public void func(List<String> ls){
    ...
}
public void func(List<Integer> ls){
    ...
}

这会让编译器报错:
Method test(List<String>) has the same erasure test(List<E>) as another method in type TR

这时候除非改变方法的返回值:

public String func(List<String> ls){
    ...
}
public int func(List<Integer> ls){
    ...
}

这样就可以通过编译了(但是这不可是一般意义上的“重载”啊)

为什么“范型参数”不可以作为重载的判据?为什么返回值在有范型的方法中却可以像“方法签名”一样用来区分两个不同的方法?这涉及到简单的编译原理——
Java函数的方法签名包括方法名称、参数类型以及参数顺序;但在字节码中,特征签名还包括了方法的返回值以及受查异常表,这就是为什么在class文件中,其他都相同仅仅返回值不同的两个方法能共存的原因(不懂请回头看那个小结范型的第4条机下面的注解)

结论:1. 使用范型可以代替多个方法的重载;2. 重载同样不关心范型的类型参数,因为在编译期被类型擦出根本看不出来;3. 有范型参与的方法可以用返回值来区分,类似于方法签名
(请将这三点作为补充,加到之前关于范型小结的那部分

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:C马雯娟 返回首页