重载:多个方法具有相同
的名字,但有不同的
参数列表
方便client(客户端)调用,client可用不同的参数列表,调用同样的函数
比如想要定义加法的方法, 让它可以计算不同类型的数之和,有不同类型的返回值,可以如下定义:
public int add(int x, int y) {
return x + y;
}
public double add(double x, double y) {
return x + y;
}
如果没有重载机制,那么想要定义两个具有相同/相似功能的方法,必须用不同函数名加以区分,如add1,add2,去定义和记住这些方法名字,对开发者和使用者都是一种负担。
重载是一种静态多态,根据参数列表进行最佳匹配,做静态类型检查,在编译阶段时决定要具体执行哪个方法。
与之相反,重写方法则是在运行时进行动态检查。
关于这一点,首先需要明白两个概念:
绑定:将调用的名字与实际的方法名字联系起来(可能很多个)
分派:具体执行哪个方法(early binding → static dispatch)
提前/静态绑定(Early/Static binding)
每当一个方法的绑定发生时,所绑定的类型由编译器在编译时确定,然后绑定发生。
推迟/动态绑定(Late/Dynamic binding)
在重写父类和子类具有相同的方法时,对象的类型决定了要执行的方法。 对象的类型在运行时确定。
类型 | 分派原理 | 发生阶段 | 应用场景 |
---|---|---|---|
静态分派 | 根据变量的静态类型 | 编译期(不由Java虚拟机执行) | Overload |
动态分派 | 根据变量的动态类型 | 运行期(由Java虚拟机执行) | Override |
Overload方法:提前绑定和静态分派。
编译阶段即可确定要执行哪个具体操作。这个主要是针对多态性而言的。笔者理解为,如果发生了继承的情况,子类重载了父类的方法,由于参数列表不同,编译阶段可以很快检查并绑定到对应的方法,所以采取了这种early binding+static dispatch的机制。
Override方法: 推迟绑定和动态分派。
如果子类重写了父类的方法,编译阶段不知道对象类型,需要进行推迟绑定,在运行阶段决定具体执行哪个方法。
如果这一点不理解可以暂时跳过,但需要了解在哪个阶段进行检查与确定执行方法。即重载方法在编译时做静态类型检查,决定执行哪一个方法;而重写方法在运行时进行动态检查,并决定执行哪个方法。
注意到,重载规则中最重要的也是最本质的是第一条规则:
重载方法的参数列表必须改变,指的是参数个数/参数类型发生改变
比如
//方法1(原方法)
public void changeSize(int size, String name, float pattern) { }
//方法2
public void changeSize(int size, String name) { }
//方法3
public void changeSize(int length, String pattern, float size){ }
//方法4
public boolean changeSize(int size, String name, float pattern) { }
方法2即为方法1的重载方法,方法3、4均不是方法1的重载方法,参数列表(指参数类型和参数个数)与方法1相同
方法3仅仅改变了参数变量名称,没有改变参数类型和个数
方法4只是改变了方法返回类型,也没有改变参数列表
下面我们来看一组子类重载父类方法的例子,加深我们的理解。
注意,可以子类中重载,也可在同一个类内重载(像前述add方法可以放在同一个类中);并不是像重写一样必须发生在父类和子类之间。
首先定义两个类Animal和Horse,Horse类中重载了Animal中的eat方法,需要传入一个字符串变量
public class Animal {
public void eat() {
System.out.println("undefined");
}
}
class Horse extends Animal{
public void eat(String food) {
System.out.println(food);
}
}
我们进行如下测试
//测试1
Animal a = new Animal();
a.eat();
输出结果:undefined,调用Animal中的无参eat方法
//测试2
Horse h = new Horse();
h.eat();
输出结果:undefined,调用Horse类中继承的无参eat方法
//测试3
Animal ah = new Horse();
ah.eat();
输出结果:undefined,由于没有参数,调用了Animal类中的无参eat方法
//测试4
Horse he = new Horse();
he.eat("Apples");
输出结果:Apples,调用Horse类中有参方法
//测试5
Animal a2 = new Animal();
a2.eat("treats");
编译报错:The method eat() in the type Animal is not applicable for the arguments (String),Animal类中没有带参数的eat方法,这里也再次说明了重载是一种静态多态,在编译时期进行静态检查。
//测试6
Animal ah2 = new Horse();
ah2.eat("Carrots");
编译报错:The method eat() in the type Animal is not applicable for the arguments (String),Animal类中没有带参数的eat方法,尽管ah2具有多态性,但重载方法基于early binding和静态分派,编译时会做静态检查,到Animal的方法里去找带参数的eat,如果找到然后进行绑定,但是显然此处静态检查是出错的(找不到带参数的eat),所以编译报错。
重载(Overload) | 重写(Override) | |
---|---|---|
参数列表 | 必须改变 | 必须不变 |
返回值类型 | 可以改变 | 必须不变 |
异常 | 可以改变 | 要么不抛出异常,要么抛出与父类方法相同的异常或该异常的子类 |
访问权限 | 可以改变 | 子类的方法的访问权限不能小于父类 |
调用 | 引用类型确定选择哪个重载版本(基于声明的参数类型),在编译时发生 | 对象类型(堆上实际实例的类型)决定了选择哪个方法,发生在运行时 |