繼承是面向對象三大特性之一,但其底層實現機制常被忽視。本文通過一個完整範例,從 JVM 視角深度剖析類載入、物件建構、方法調用(動態綁定)、虛方法表、變數存取等核心機制。
// 基類
public class Base {
public static int s;
private int a;
static {
System.out.println("基類靜態代碼塊, s: " + s);
s = 1;
}
{
System.out.println("基類實例代碼塊, a: " + a);
a = 1;
}
public Base() {
System.out.println("基類構造方法, a: " + a);
a = 2;
}
protected void step() {
System.out.println("base s: " + s + ", a: " + a);
}
public void action() {
System.out.println("start");
step();
System.out.println("end");
}
}
// 子類
public class Child extends Base {
public static int s;
private int a;
static {
System.out.println("子類靜態代碼塊, s: " + s);
s = 10;
}
{
System.out.println("子類實例代碼塊, a: " + a);
a = 10;
}
public Child() {
System.out.println("子類構造方法, a: " + a);
a = 20;
}
@Override
protected void step() {
System.out.println("child s: " + s + ", a: " + a);
}
}
// 使用端
public static void main(String[] args) {
System.out.println("---- new Child()");
Child c = new Child();
System.out.println("\n---- c.action()");
c.action();
Base b = c;
System.out.println("\n---- b.action()");
b.action();
System.out.println("\n---- b.s: " + b.s);
System.out.println("\n---- c.s: " + c.s);
}
---- new Child() 基類靜態代碼塊, s: 0 子類靜態代碼塊, s: 0 基類實例代碼塊, a: 0 基類構造方法, a: 1 子類實例代碼塊, a: 0 子類構造方法, a: 10 ---- c.action() start child s: 10, a: 20 end ---- b.action() start child s: 10, a: 20 end ---- b.s: 1 ---- c.s: 10
每個類在方法區儲存:
Child 物件記憶體結構: +------------------+ | Base.a (私有) | ← 偏移量 0 | Child.a (私有) | ← 偏移量 4 +------------------+ | 類型指標 → Child 類資訊 (方法區) +------------------+
基類實例代碼塊, a: 0 // Base 實例區塊,a=0→1 基類構造方法, a: 1 // Base 構造器,a=1→2 子類實例代碼塊, a: 0 // Child 實例區塊,Child.a=0→10 子類構造方法, a: 10 // Child 構造器,Child.a=10→20
繼承鏈深度為 d,每次方法調用需向上遍歷最多 d 次查找 → 效能差。
Child 類虛方法表: +----------------+------------------+ | 方法簽名 | 實際程式碼地址 | +----------------+------------------+ | action() | Base.action() | ← 繼承自父類 | step() | Child.step() | ← 覆寫 | toString() | Object.toString()| ← 繼承自 Object | ... | ... | +----------------+------------------+ Base 類虛方法表: +----------------+------------------+ | action() | Base.action() | | step() | Base.step() | | toString() | Object.toString()| +----------------+------------------+
物件.虛方法表[方法索引] → O(1) 直接定位b.s → 編譯器解析為 Base.s → 輸出 1 c.s → 編譯器解析為 Child.s → 輸出 10
b.a 存取 Base.a,c.a 存取 Child.a| 機制 | 核心原則 | 關鍵點 |
|---|---|---|
| 類載入 | 父優先,靜態初始化父→子 | 靜態變數各自獨立,不共享 |
| 物件建構 | 記憶體含所有父類欄位,初始化父→子 | 構造器鏈隱式 super() |
| 方法調用 | 動態綁定(實際類型查找) | 虛方法表優化為 O(1) |
| 變數存取 | 靜態綁定(宣告類型決定) | 靜態/實例變數皆不多態 |
深入理解繼承的 JVM 級實現,能幫助開發者做出更好的架構決策:在正確的地方使用繼承,在更多地方選擇組合。