Java 多态
Java 多态
Section titled “Java 多态”多态 (Polymorphism),意为“多种形式”,是面向对象编程 (OOP) 的核心概念。它允许将不同类的对象视为公共超类或接口的对象来处理。最常见的用法是一个超类引用变量引用其子类之一的对象。
一个对象是多态的,如果它通过一个以上的 IS-A 测试(由继承和接口实现决定)。在 Java 中,每个对象本质上都是多态的,因为它 IS-A 自身,并且它 IS-A Object。
引用变量和对象:
Section titled “引用变量和对象:”需要记住的关键点:
- 对象通过引用变量访问。
- 引用变量有一个声明类型(类或接口),该类型不能改变。
- 引用变量的类型决定了在编译时可以调用哪些方法。
- 引用变量可以引用其声明类型的对象或其声明类型的任何子类型(子类或实现类)。
- 内存中的单个对象可以被不同兼容类型的多个引用变量引用。
示例:IS-A 关系
Section titled “示例:IS-A 关系”考虑这些定义:
interface Vegetarian {}class Animal {}class Deer extends Animal implements Vegetarian {}一个 Deer 对象表现出多态性:
DeerIS-AAnimal(由于extends)DeerIS-AVegetarian(由于implements)DeerIS-ADeer(它自身的类型)DeerIS-AObject(隐式)
因此,以下赋值是有效的:
// 创建一个 Deer 对象Deer deerObj = new Deer();
// 将 Deer 对象赋给其超类型的引用Animal animalRef = deerObj; // OK: Deer 是 AnimalVegetarian vegetarianRef = deerObj; // OK: Deer 是 VegetarianObject objectRef = deerObj; // OK: Deer 是 ObjectDeer deerRef = deerObj; // OK: Deer 是 Deer
// 所有引用 (deerRef, animalRef, vegetarianRef, objectRef) 都指向内存中同一个 Deer 对象。方法调用和虚方法
Section titled “方法调用和虚方法”多态性与方法重写结合时发挥巨大作用。当子类提供了从超类继承的方法的特定实现时,它就重写了超类方法。
Java 使用虚方法调用 (也称为动态方法分派或晚期绑定)。这意味着:
- 编译时: 编译器根据引用变量的类型检查方法调用是否有效。(例如,
animalRef.eat()仅当Animal类有eat()方法时才有效)。 - 运行时: JVM 根据引用在那一刻指向的实际对象的类型决定执行哪个实际的方法实现。如果实际对象是
Deer,即使引用是Animal类型,也会调用该方法的Deer版本。
所有非静态、非 final、非 private 方法在 Java 中都是隐式地虚方法。
示例:员工工资检查
Section titled “示例:员工工资检查”考虑一个 Employee 超类和一个 Salary 子类,Salary 子类重写了 mailCheck 方法。
public class Employee { private String name; private String address; private int number;
public Employee(String name, String address, int number) { System.out.println("Constructing an Employee"); this.name = name; this.address = address; this.number = number; }
// 基本的 mailCheck 实现 public void mailCheck() { System.out.println("Mailing check to " + this.name + " at " + this.address); }
@Override public String toString() { return name + " " + address + " " + number; }
// 为简洁起见,省略了 Getter 和 Setter... public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; }}/* 文件: Salary.java */public class Salary extends Employee { private double salary; // 年薪
public Salary(String name, String address, int number, double salary) { super(name, address, number); // 调用 Employee 构造函数 setSalary(salary); }
// 重写的 mailCheck 实现 @Override public void mailCheck() { System.out.println("Within Salary's mailCheck method:"); System.out.println("Mailing check to " + getName() + " (Salary: " + salary + ") at " + getAddress()); }
public double getSalary() { return salary; }
public void setSalary(double newSalary) { if (newSalary >= 0.0) { salary = newSalary; } else { System.err.println("Salary cannot be negative."); } }
public double computePay() { System.out.println("Computing salary pay for " + getName()); return salary / 52; // 周薪 }}演示多态行为:
/* 文件: VirtualDemo.java */public class VirtualDemo { public static void main(String[] args) { // 引用和对象都是 Salary 类型 Salary salRef = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 52000.00);
// 引用是 Employee 类型,但对象是 Salary 类型 (多态) Employee empRef = new Salary("John Adams", "Boston, MA", 2, 60000.00); // 使用之前的 Salary 对象的另一种方式: // Employee empRef = salRef;
System.out.println("Calling mailCheck using Salary reference:"); // 使用 Salary 引用调用 mailCheck: salRef.mailCheck(); // 调用 Salary 的 mailCheck()
System.out.println("\nCalling mailCheck using Employee reference:"); // 使用 Employee 引用调用 mailCheck: empRef.mailCheck(); // 由于运行时对象类型是 Salary,调用了 Salary 的 mailCheck()!
// 如果取消注释,将导致编译错误: computePay() 不在 Employee 类中 // empRef.computePay();
// 要通过 empRef 调用 Salary 特定的方法,需要进行显式类型转换: if (empRef instanceof Salary) { Salary specificSalRef = (Salary) empRef; double weeklyPay = specificSalRef.computePay(); System.out.println("\nJohn Adams weekly pay: " + weeklyPay); // John Adams 的周薪: } }}
class Vehicle {}class Car extends Vehicle {}输出:
Constructing an EmployeeConstructing an EmployeeCalling mailCheck using Salary reference:Within Salary's mailCheck method:Mailing check to Mohd Mohtashim (Salary: 52000.0) at Ambehta, UP
Calling mailCheck using Employee reference:Within Salary's mailCheck method:Mailing check to John Adams (Salary: 60000.0) at Boston, MA
Computing salary pay for John Adams
John Adams weekly pay: 1153.846...解释:
- 当调用
salRef.mailCheck()时,编译时类型 (Salary) 和运行时类型 (Salary) 都有mailCheck()。执行Salary版本的代码。 - 当调用
empRef.mailCheck()时: -
- 在编译时,编译器检查
Employee类中是否有mailCheck()方法。它找到了,所以代码编译通过。
- 在编译时,编译器检查
-
- 在运行时,JVM 看到
empRef实际指向一个Salary对象。因此,它执行从Salary类重写的mailCheck()方法。
- 在运行时,JVM 看到
- 这种运行时选择合适的重写方法是虚方法调用和多态的精髓所在。
- 调用子类特有的方法(如
computePay)需要使用instanceof检查类型并进行引用变量的类型转换。
多态性使得代码更灵活、更具可扩展性。您可以编写操作超类或接口类型的方法,它们将与传入的任何当前或未来的子类对象正确工作,并调用适当的特殊化行为。