多態是物件導向三大特性之一,但其背後的類型轉換規則與方法綁定機制往往是初學者困惑、資深開發者面試必考的重點。本文通過代碼實驗與 JVM 視角,徹底講清這兩大核心機制。
Animal animal = new Cat(); // 子類實例賦給父類引用 animal.cry(); // 動態綁定調用 Cat.cry()
Animal animal = new Cat();
// 錯誤示範:Animal a2 = new Animal(); Cat c = (Cat)a2; // 運行期 ClassCastException
// 正確姿勢
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse(); // 調用子類特有方法
}
鐵律:只有經過向上轉型的子類對象,才能向下轉型回原類型。父類實例絕不能強轉為子類。
二、多態調用的三個必要條件
- 繼承關係(extends/implements)
- 方法重寫(Override,非重載)
- 父類引用指向子類對象
三、靜態綁定 vs 動態綁定:JVM 如何分派方法?
1. 靜態綁定(編譯期確定)
以下方法調用在編譯期即可確定唯一目標:
private 方法(子類不可見)static 方法(屬於類,非對象)final 方法(禁止覆寫)編譯器直接生成 invokestatic / invokespecial 字節碼指令。
普通實例方法調用:
對應字節碼指令:invokevirtual。
為避免每次調用都遍歷繼承鏈,JVM 為每個類構建方法表:
// Animal 方法表 cry() -> Animal.cry() getName() -> Animal.getName() // Cat 方法表(覆寫 cry、新增 climbTree) cry() -> Cat.cry() // 覆寫項指向自身實現 getName() -> Animal.getName() // 繼承項指向父類實現 climbTree() -> Cat.climbTree() // 新增項
調用時直接按索引定位,極大提升分派速度。
class Master {
public void feed(Animal animal, Food food) {
animal.eat(food); // 一個參數處理所有動物,無需重載
}
}
// 調用
master.feed(new Cat(), new Fish()); // 貓吃魚
master.feed(new Dog(), new Bone()); // 狗啃骨頭
多態消除了大量 if/else instanceof 判斷,實現開閉原則:新增動物類無需修改 Master 代碼。
| 概念 | 關鍵點 |
|---|---|
| 向上轉型 | 隱式、安全、賦值兼容 |
| 向下轉型 | 顯式強轉、需 instanceof 檢查、僅限曾向上轉型的對象 |
| 靜態綁定 | private/static/final/構造器、編譯期確定、invokestatic/invokespecial |
| 動態綁定 | 普通實例方法、運行期按實際類型分派、invokevirtual、方法表優化 |
| 多態前提 | 繼承 + 重寫 + 父類引用指向子類對象 |
掌握類型轉換規則與綁定機制,不僅能避免 ClassCastException,更能從 JVM 執行層面理解代碼性能特徵,寫出更健壯、高效的多態代碼。