Java中的重载(Overload)机制详解及与重写(Override)的区别

重载

重载的定义

重载:多个方法具有相同的名字,但有不同的参数列表

重载的好处

方便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. 必须有不同的参数列表
  2. 可以有相同/不同的返回值类型
  3. 可以有相同/不同的访问权限(public/private/protected)
  4. 可以声明新的异常
  5. 可以在同一个类内重载,也可在子类中重载

注意到,重载规则中最重要的也是最本质的是第一条规则
重载方法的参数列表必须改变,指的是参数个数/参数类型发生改变
比如

//方法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)
参数列表 必须改变 必须不变
返回值类型 可以改变 必须不变
异常 可以改变 要么不抛出异常,要么抛出与父类方法相同的异常或该异常的子类
访问权限 可以改变 子类的方法的访问权限不能小于父类
调用 引用类型确定选择哪个重载版本(基于声明的参数类型),在编译时发生 对象类型(堆上实际实例的类型)决定了选择哪个方法,发生在运行时

猜你喜欢

转载自blog.csdn.net/weixin_45577864/article/details/118360448