Java多态面试知识点总结

218 阅读5分钟

多态

概述

我们继续使用继承中的的示例代码

public class Employee {
    private String name;
    private double salary;
    private LocalDate hireDay;

    public Employee(String name, double salary, int year, int month, int day) {
        this.name = name;
        this.salary = salary;
        this.hireDay = LocalDate.of(year, month, day);
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public LocalDate getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", salary=" + salary +
                ", hireDay=" + hireDay +
                '}';
    }
}
public class Manager extends Employee {
    private double bonus;

    public Manager(String name, double salary, int year, int month, int day) {
        // Manager 类的构造器不能访问 Employee 类的私有域,所以必须利用 Employee 类的构造器对这部分私有域进行初始化, 且使用 super 调用构造器的语句必须是子类构造器的第一条语句。
        super(name, salary, year, month, day);
        this.bonus = 0;
    }

    public void setBonus(double bonus) {
        this.bonus = bonus;
    }

    public double getSalary() {
        double baseSalary = super.getSalary();
        return baseSalary + bonus;
    }
}

测试代码:

public class ManagerTest {
    public static void main(String[] args) {
        Manager boss = new Manager("Jack", 8000,  2020, 4, 12);
        boss.setBonus(2000);

        Employee employee1 = boss;
        Employee employee2 = new Employee("Alice", 2000, 2020, 4, 2);

        System.out.println(employee1.getSalary());
        System.out.println(employee2.getSalary());
    }
}
// output
// 10000.0
// 2000.0

在上述测试代码中,我们可以看到声明为 Employee 的变量既可以引用 Employee 类型的对象,也可以引用 Manager 类型的对象,当引用 Employee 对象时调用其 getSalary方法, 当引用 Manager 对象时调用 Manager 对象的方法。

像上述情形,一个对象变量可以只是多种实际类型的现象称为多态(polymorphism),在运行时能够 自动地选择调用哪个方法的现象称为动态绑定(dynamic binding)。

有一个用来判断是否应该设计为继承关系的简单规则,这就是 ”is-a“ 规则,它表明子类的每个对象也是超类的对象,例如上面的例子:每个 管理者(Manager)是雇员(Employee)。is-a 规则的另一种表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换,如。

Employee e;
e = new Employee();
e = new Manager();

在 Java 程序设计语言中,对象变量是多态的,一个 Employee 变量既可以引用一个 Employee 对象,也可以引用一个 Employee 类的任何一个子类的对象。

但是需要主要注意不能将超类的引用复制给子类变量,如

Employee e = new Employee();
Manager m = e; // error

因为不是每个雇员(Employee)都是经理(Manager),不是 is-a 的关系,在代码层面也很好理解,如果这样赋值,很容易造成 m 调用 Manager 专属的方法引起错误。

方法调用的过程

我们假设现在调用 x.f(args) 隐式参数 x 声明为类 C 的一个对象,下面是调用过程的详细描述:

  • 编译器获取所有可能被调用的候选方法:编译器查看对象的声明类型(C)和方法名(f),此过程编译器会一一列举所有 C 类中名为 f 的方法和超类中访问属性为public 且名为 f 的方法。

  • 编译器获取需要调用的方法名字和参数类型:编译器会查看调用方法时提供的参数类型, 如**果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法,这个过程被称为重载解析(overloading resolution)。**由于允许类型转换(int 可以转换为 double, Manager 可以转换为 Employee等),此过程会很复杂,如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后存在多个方法与之匹配,就会报告一个错误。

  • 静态绑定(static bingding):如果方法为 private, static, final修饰的方法,或者是构造器方法,那么编译器可以准确地知道应该调用哪个方法。

  • 动态绑定(dynamic binding):不是静态绑定的方法都将采取动态绑定,调用的方法依赖与隐式参数的实际类型,在运行时实现动态绑定,此时虚拟久会调用与 x 所引用对象的实际类型最合适的那个类的方法:即假设 x 的实际类型为 D, D 为 C 的子类,且在 D 类中存在方法签名相同的方法,此时就会调用 D 中的方法,否则在 D 的超类中寻找该方法。

    • 由于每次调用方法都有进行搜索,时间开销很大,因此,虚拟机预先为每个类常见了一个方法表(method table),其中列出了所有方法的签名和实际调用的方法,这样,在真正调用方法的时候,虚拟机仅查找这个表就行了。

了解规则之后,我们可以看一下测试代码中 employee1.getSalary()的调用过程:

// method table
Employee:
	getName() -> Employee.getName()
	getSalary() -> Employee.getSalary()
	getHireDay() -> Employee.getHireDay()
	raiseSalary() -> Employee.raiseSalary()
	
Manager:
	getName() -> Employee.getName()
	getSalary() -> Manager.getSalary()
	getHireDay() -> Employee.getHireDay()
	raiseSalary() -> Employee.raiseSalary()
	setBonus() -> Manager.setBonus()
  • 编译器获取方法签名 getSalary()
  • 由于该方法不是 private, static, final 或构造器方法,因此会采用动态绑定,虚拟机在运行时提取方法表
  • 虚拟机搜索 定义了 getSalary 签名的类,此时虚拟机已经知道调用哪个方法。
  • 虚拟机调用方法。

注意:

  • 方法签名:方法名和参数列表,(f(int) 和f(String) :方法签名不同,但方法名相同)如果在子类中定义了和超类中签名相同的方法,此时会出现方法重写,但是在方法重写时一定要保证返回类型的兼容性,即允许子类将重写方法的返回类型定义为原返回类型的子类型。

  • 方法重写时子类方法不能低于超类方法的可见性。

Reference

1.[Java核心技术·卷 I(原书第10版)](