Java 物件導向三大特性之一「多態」的基石是:向上轉型只改變引用類型,不改變堆中對象實際類型與佈局。本文通過原始碼實驗、記憶體佈局分析、字節碼視角、實戰陷阱四個維度深度證明這一核心結論。
class Father {
int workNum;
Father(int i) { workNum = i; }
void show() { System.out.println("Father " + workNum); }
}
class Child extends Father {
int studyNum;
Child(int i) { super(i); studyNum = i; }
@Override void show() { System.out.println("Child " + studyNum); }
}
public class CastTest {
public static void main(String[] args) {
Child c = new Child(23);
System.out.println("c.hashCode(): " + c.hashCode());
System.out.println("c.getClass(): " + c.getClass());
Father f = (Father) c; // 向上轉型
System.out.println("f.hashCode(): " + f.hashCode());
System.out.println("f.getClass(): " + f.getClass()); // 關鍵!
c.show(); // Child 23
f.show(); // Child 23 (動態分派)
System.out.println("c == f : " + (c == f)); // true
}
}
c.hashCode(): 123456789 c.getClass(): class Child f.hashCode(): 123456789 f.getClass(): class Child // 關鍵證據:對象類型未變! c.show(): Child 23 f.show(): Child 23 // 多態:父類引用調用子類覆寫方法 c == f : true // 同一個堆對象
┌─────────────────────────────────────────────────────────────┐ │ Heap Memory │ ├─────────────────────────────────────────────────────────────┤ │ Child Object (實際類型永遠是 Child) │ │ ┌──────────────┬──────────────┬──────────────┐ │ │ │ Object Header │ Father Fields │ Child Fields │ │ │ │ (Mark Word, │ workNum = 23 │ studyNum=23 │ │ │ │ Klass Pointer)│ │ │ │ │ └──────────────┴──────────────┴──────────────┘ │ │ ▲ │ │ │ │ │ ┌──────────┴──────────┐ │ │ │ │ │ │ Reference c Reference f │ │ Type: Child Type: Father │ │ (指向同一塊記憶體) │ └─────────────────────────────────────────────────────────────┘
關鍵結論:
getClass()、instanceof、方法表查找均為 Child// Father f = (Father) c; ALOAD 1 // 載入 c 引用 CHECKCAST Father // 類型檢查:驗證 c 實際類型是否為 Father 或其子類 ASTORE 2 // 存入 f 引用槽 // f.show(); ALOAD 2 // 載入 f INVOKEVIRTUAL Father.show // 動態分派:根據對象頭 Klass Pointer 查找 Child.show
CHECKCAST 僅做類型安全檢查,通過則直接使用原引用地址,零拷貝、零分配、零轉換。
| 階段 | 引用類型 | 可訪問成員 | 方法調用綁定 |
|---|---|---|---|
| 編譯期 (靜態) | Father | Father 声明的欄位/方法 (含繼承的 public/protected) | 靜態綁定 (private/static/final/建構子) |
| 運行期 (動態) | Child (實際對象) | 完整 Child 結構 (Father 可見部分 + Child 獨有) | 動態分派 (虛方法):查找實際類型方法表 |
class Father { String name = "Father"; }
class Child extends Father { String name = "Child"; }
Father f = new Child();
System.out.println(f.name); // "Father"!欄位靜態綁定,不隨多態
System.out.println(((Child)f).name); // "Child" 強制轉型訪問子類欄位
規範:欄位私有化,提供 getter/setter,避免遮蔽。
Father f = new Father();
Child c = (Child) f; // 編譯通過,運行期 ClassCastException!
// 安全模式
if (f instanceof Child) {
Child c = (Child) f; // Java 16+ Pattern Matching: if (f instanceof Child c) ...
}
class Father { void process(Object o) { ... } }
class Child extends Father {
// 重載!非覆寫。Father f = new Child(); f.process("str") → Father.process(Object)
void process(String s) { ... }
}
@Override 註解強制編譯器驗證覆寫規則,防止誤重載。
// Java 16+ 模式匹配 (JEP 394)
if (obj instanceof String s) { // 自動轉型、賦值、作用域安全
System.out.println(s.length());
}
// 訪問者模式替代 instanceof 鏈
interface Visitor { void visit(Child c); void visit(Other o); }
interface Element { void accept(Visitor v); }
class Child extends Father implements Element {
public void accept(Visitor v) { v.visit(this); }
}
向上轉型是「視角收窄」,而非「對象變形」。堆中對象從出生 (new) 到死亡 (GC) 類型永恆不變。多態的本質是:引用類型決定「能看見什麼」(編譯期),對象實際類型決定「具體做什麼」(運行期)。理解這一點,是掌握 Java 物件模型、避免 ClassCastException、設計優雅繼承體系的關鍵。