多態是面向對象三大特性 (封裝、繼承、多態) 中最具威力、也是最易混淆的概念。本文從「三大支柱」切入,結合內存模型、方法調用規則、向上/向下轉型實戰,為你構建完整的多態知識體系。
基礎:子類繼承父類非私有成員
class Animal {
void eat() { System.out.println("動物進食"); }
}
class Bird extends Animal {
// 繼承了 eat(),可直接調用
}
extends 關鍵字實現核心:子類重新定義父類方法
class Bird extends Animal {
@Override
void eat() {
System.out.println("鳥吃蟲子"); // 具體實現
}
}
@Override 註解編譯期檢查形式:向上轉型
Animal animal = new Bird(); // 核心語法
animal.eat(); // 輸出: 鳥吃蟲子
(Bird) animal
棧內存 (引用) 堆內存 (對象) 方法區 (類元數據)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Animal a │ ───► │ Bird 对象 │ │ Animal.class │
│ │ │ - vptr ──────┼───► │ - eat() │
└──────────────┘ └──────────────┘ │ Bird.class │
│ - eat() ◄───┐│
└──────────────┘│
│
┌─────────────────────────────────┘
▼
虛方法表 - 動態分派核心
┌──────────────────┐
│ Animal.eat → 0x1 │
│ Bird.eat → 0x2 │ ◄─── 運行時解析到此入口
└──────────────────┘
| 場景 | 父類方法 | 子類方法 | 調用結果 |
|---|---|---|---|
| 重寫 | 有 | 有 (同簽名) | ✅ 調用子類方法 (動態分派) |
| 未重寫 | 有 | 無 | ✅ 調用父類方法 (繼承) |
| 子類特有 | 無 | 有 | ❌ 父類引用無法調用 (編譯錯誤) |
| 靜態方法同名 | static | static | ⚠️ 隱藏而非重寫,調用引用類型方法 |
class Animal {
public void eat() { System.out.println("動物進食"); }
public static void breathe() { System.out.println("動物呼吸"); }
}
class Bird extends Animal {
@Override
public void eat() { System.out.println("鳥吃蟲子"); }
public static void breathe() { System.out.println("鳥呼吸"); } // 隱藏
public void fly() { System.out.println("鳥飛翔"); } // 子類特有
}
public class PolyTest {
public static void main(String[] args) {
Animal a = new Bird();
a.eat(); // ✅ 輸出: 鳥吃蟲子 (動態分派)
a.breathe(); // ⚠️ 輸出: 動物呼吸 (靜態分派,看引用類型)
// a.fly(); // ❌ 編譯錯誤: Animal 無 fly 方法
// 向下轉型調用子類特有方法
if (a instanceof Bird) {
Bird b = (Bird) a;
b.fly(); // ✅ 輸出: 鳥飛翔
}
}
}
Animal animal = new Bird(); // 隱式向上轉型
Animal animal2 = (Animal) new Bird(); // 顯式向上轉型 (冗余)
// 好處:統一管理異構對象
List<Animal> zoo = Arrays.asList(
new Bird(), new Fish(), new Mammal()
);
zoo.forEach(Animal::eat); // 多態調用各自實現
Animal animal = new Bird();
// ❌ 危險:運行時可能拋 ClassCastException
Fish fish = (Fish) animal;
// ✅ 安全模式:instanceof 守衛
if (animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly();
}
// ✅ Java 14+ 模式匹配 (更簡湜)
if (animal instanceof Bird bird) {
bird.fly();
}
// ❌ 違反開閉原則:新增動物需修改此方法
void feedAnimal(Animal animal) {
if (animal instanceof Bird) { ... }
else if (animal instanceof Fish) { ... }
// ...
}
// ✅ 符合開閉原則:利用多態擴展
interface Feedable {
void feed(Food food);
}
class Bird implements Feedable {
public void feed(Food food) { /* 鳥餵食邏輯 */ }
}
class ZooKeeper {
public void feed(Feedable animal, Food food) {
animal.feed(food); // 多態調用,無需知道具體類型
}
}
class Parent {
String name = "Parent";
void print() { System.out.println("Parent.print()"); }
}
class Child extends Parent {
String name = "Child"; // 字段遮蔽,非重寫
void print() { System.out.println("Child.print()"); } // 方法重寫
}
Parent p = new Child();
System.out.println(p.name); // 輸出: Parent (字段看引用類型)
p.print(); // 輸出: Child.print() (方法看對象類型)
final,類加 final 禁止繼承多態的本質是 「同一接口,不同實現」。它將 做什麼 (接口) 與 怎麼做 (實現) 解耦,讓代碼具備擴展性與彈性。掌握 繼承→重寫→向上轉型→動態分派 這條主線,並警惕 靜態綁定、字段遮蔽、向下轉型風險 這三大陷阱,即可駕馭多態這把雙刃劍。