經典面試題,考察編譯時類型與運行時類型、方法調用綁定機制(靜態綁定 vs 動態綁定)、向上轉型後的成員訪問規則。
public class PolymorphismDemo {
static class Animal {
public void move() {
System.out.println("動物可以移動");
}
}
static class Dog extends Animal {
@Override
public void move() {
System.out.println("狗可以跑和走");
}
public void bark() {
System.out.println("狗可以吠叫");
}
}
public static void main(String[] args) {
Animal a = new Animal(); // Animal 對象
Animal b = new Dog(); // Dog 對象,向上轉型
Dog c = new Dog(); // Dog 對象
a.move(); // 輸出:動物可以移動
b.move(); // 輸出:狗可以跑和走(動態綁定)
// b.bark(); // 編譯錯誤:Animal 類無 bark()
c.bark(); // 輸出:狗可以吠叫
}
}
| 引用變量 | 聲明類型(編譯時類型) | 實際對象類型(運行時類型) | 可調用方法範圍 |
|---|---|---|---|
| a | Animal | Animal | Animal 定義的所有方法 |
| b | Animal | Dog | 僅 Animal 定義的方法(move() 經過重寫,動態分派到 Dog.move()) |
| c | Dog | Dog | Dog 定義的所有方法(含繼承自 Animal 的) |
b.bark() 編譯失敗,因為 Animal 類中沒有 bark() 方法簽名。b.move() 調用 Dog.move(),因為 JVM 根據對象實際類型進行動態方法分派(虛方法表查找)。Animal b = new Dog() 隱式向上轉型;若需調用 bark(),需 ((Dog) b).bark()(運行前建議 instanceof 判斷)。Animal 和 Dog 都有 name 字段,b.name 訪問的是 Animal.name(編譯期綁定)。棧(Stack) 堆
b (Animal引用) ──────────► Dog對象
├─ 繼承自 Animal 的字段
├─ Dog 獨有字段
└─ 方法表指針 → Dog.move(), Dog.bark()
c (Dog引用) ──────────► 同一 Dog對象(或另一個)
List list = new ArrayList<>(); 而非 ArrayList list = new ArrayList<>(); 。instanceof / 模式匹配 (Java 16+ if (obj instanceof Dog d) d.bark();)。interface Animal {
default void breathe() { System.out.println("呼吸"); }
}
interface Pet {
default void play() { System.out.println("玩耍"); }
}
class Dog implements Animal, Pet {
// 繼承默認方法,可選擇重寫
}
Animal a = new Dog();
a.breathe(); // OK
// a.play(); // 編譯錯誤:Animal 無 play()
Pet p = new Dog();
p.play(); // OK
掌握「聲明類型決定能調什麼,實際類型決定調用誰」這一核心法則,是寫出健壯、可擴展面向對象代碼的前提。