定義:父類引用指向子類對象 (Animal animal = new Dog())。
前提:繼承或實現關係。
核心規則:編譯看左邊(引用類型),運行看右邊(對象類型)。
| 成員類型 | 多態調用行為 | 說明 |
|---|---|---|
| 非私有實例方法 | 動態綁定:運行期調用子類重寫版本 | 虛擬方法調用,多態核心 |
| 私有方法 | 靜態綁定:調用父類版本 | 不可繼承/重寫,無多態 |
| 靜態方法 | 靜態綁定:調用引用類型版本 | 屬於類,不屬於對象,隱藏而非重寫 |
| 構造方法 | 無多態 | 子類構造隱式調用父類構造 |
| 實例字段 | 靜態綁定:訪問引用類型聲明字段 | 字段不參與多態,被隱藏 |
class Animal {
String name = "動物";
public void sound() { System.out.println("動物叫聲"); }
public void breathe() { System.out.println("呼吸"); }
}
class Dog extends Animal {
String name = "狗"; // 字段隱藏
@Override
public void sound() { System.out.println("汪汪汪"); } // 方法重寫
public void fetch() { System.out.println("撿球"); } // 子類特有
}
class Cat extends Animal {
@Override
public void sound() { System.out.println("喵喵喵"); }
}
public class PolymorphismDemo {
public static void main(String[] args) {
// 1. 基礎多態
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.sound(); // 汪汪汪 (動態綁定)
a2.sound(); // 喵喵喵
// 2. 字段無多態
System.out.println(a1.name); // "動物" (編譯看Animal)
// 強轉後訪問子類字段
System.out.println(((Dog)a1).name); // "狗"
// 3. 子類特有方法無法直接調用
// a1.fetch(); // 編譯錯誤
if (a1 instanceof Dog) {
((Dog)a1).fetch(); // 安全強轉後調用
}
// 4. 多態數組
Animal[] zoo = {new Dog(), new Cat(), new Animal()};
for (Animal a : zoo) a.sound(); // 統一接口,不同行為
// 5. 多態參數 (策略模式雛形)
feed(new Dog());
feed(new Cat());
}
static void feed(Animal a) { // 參數多態
System.out.println("喂食: " + a.getClass().getSimpleName());
a.sound();
}
}
// 傳統寫法 (繁瑣、易出錯)
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 重複類型
dog.fetch();
}
// JDK 16+ 模式匹配 (簡潔、安全)
if (animal instanceof Dog dog) { // 直接聲明模式變量
dog.fetch(); // 作用域僅在if塊內
}
// switch 表達式配合 (JDK 17+)
String describe = switch (animal) {
case Dog d -> "狗: " + d.name;
case Cat c -> "貓: " + c.name;
case null -> "空";
default -> "未知動物";
};
// Java 源碼 Animal a = new Dog(); a.sound(); // 字節碼 NEW Dog DUP INVOKESPECIAL Dog.()V ASTORE 1 ALOAD 1 INVOKEVIRTUAL Animal.sound ()V // 關鍵:invokevirtual
invokespecial:構造器、私有方法、父類方法 — 靜態綁定invokevirtual:普通實例方法 — 動態分發,通過虛方法表查找實際方法invokeinterface:接口方法 — 類似但查找接口方法表invokedynamic:Lambda/動態語言 — BootstrapMethod動態鏈接虛方法表:類加載階段構建,每個類一張表,存儲方法簽名 → 實際方法入口的映射。子類重寫方法時,覆蓋父類表項。調用時:對象頭 → 類指針 → 虛方法表[索引] → 方法入口。
interface PayStrategy { void pay(double amount); }
class Alipay implements PayStrategy { public void pay(double a) { /*...*/ } }
class WechatPay implements PayStrategy { public void pay(double a) { /*...*/ } }
class Order {
private PayStrategy strategy;
public void setPayStrategy(PayStrategy s) { this.strategy = s; }
public void checkout(double amt) { strategy.pay(amt); }
}
// 運行期動態切換支付方式
abstract class DataProcessor {
public final void process() { // final 防止重寫
read();
transform();
write();
}
abstract void read();
abstract void transform();
abstract void write();
}
class CsvProcessor extends DataProcessor { /* 實現三步驟 */ }
class JsonProcessor extends DataProcessor { /* 實現三步驟 */ }
| 陷阱 | 現象 | 解決方案 |
|---|---|---|
| 字段隱藏導致困惑 | 父子類同名字段,多態引用訪問父類字段 | 避免同名字段;用Getter/Setter封裝 |
| 構造器調用可覆寫方法 | 父構造器調用子類重寫方法,字段未初始化 | 構造器中只調用final/private/靜態方法 |
| 靜態方法「偽多態」 | Child.staticMethod() 隱藏 Parent.staticMethod() | 類名調用靜態方法;避免實例調用 |
| instanceof 濫用 | 到處判斷類型強轉,違背開閉原則 | 引入多態方法/訪問者模式/模式匹配 |
| 序列化破壞多態 | 反序列化後類型信息丟失 | 實現Serializable + serialVersionUID;考慮JSON序列化 |
多態是面向對象「接口隔離變化」的基石。理解動態綁定機制、訪問權限邊界、虛方法表原理、模式匹配現代語法,配合策略/模板/訪問者等模式,能編寫出可擴展、低耦合、高內聚的代碼。記住核心口訣:編譯看左,運行看右;方法動態,字段靜態;構造靜態,字段隱藏。