首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

計算機程式思維邏輯(17):繼承實現原理深度解析——類載入、物件建構、方法調用與虛方法表

     編輯:Java 綜合教學

計算機程式思維邏輯(17):繼承實現原理深度解析

繼承是面向對象三大特性之一,但其底層實現機制常被忽視。本文通過一個完整範例,從 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

三、類載入機制

1. 類資訊組成(方法區)

每個類在方法區儲存:

  • 類變數(static fields)
  • 類初始化代碼(static blocks + static field 初始化)
  • 類方法(static methods)
  • 實例變數資訊
  • 實例初始化代碼(instance blocks + 構造器)
  • 實例方法
  • 父類引用

2. 載入時序

  1. 載入 Child → 發現父類 Base 未載入 → 載入 Base
  2. 載入 Base → 發現父類 Object 未載入 → 載入 Object
  3. 執行 Base 類初始化代碼(靜態區塊、靜態欄位)
  4. 執行 Child 類初始化代碼

3. 關鍵觀察:靜態變數遮蔽

  • Base.s 與 Child.s 是獨立的兩個靜態變數
  • Child 類初始化時,Base.s 已初始化為 1,Child.s 預設 0
  • 輸出「基類靜態代碼塊, s: 0」→「子類靜態代碼塊, s: 0」驗證此點

四、物件建構過程

1. 記憶體佈局(堆)

Child 物件記憶體結構:
+------------------+
| Base.a (私有)    |  ← 偏移量 0
| Child.a (私有)   |  ← 偏移量 4
+------------------+
| 類型指標 → Child 類資訊 (方法區)
+------------------+

2. 建構執行順序

  1. 分配記憶體(含所有父類實例變數),預設值 0/null
  2. 執行 Base 實例初始化代碼(區塊 → 構造器)
  3. 執行 Child 實例初始化代碼(區塊 → 構造器)
  4. 回傳物件引用

3. 輸出驗證

基類實例代碼塊, 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

五、方法調用與動態綁定

1. c.action() 執行流程

  1. 查看 c 實際類型:Child
  2. Child 中找 action() → 無
  3. 父類 Base 中找到 action() → 執行
  4. action() 內呼叫 step()
  5. 從 Child 類型開始找 step() → 找到 Child.step() → 執行(多態)
  6. 回到 Base.action() 繼續執行

2. b.action() 相同結果

  • b 編譯型別 Base,執行時型別 Child
  • 動態綁定:根據實際物件類型查找方法
  • b 與 c 指向同一物件 → 結果相同

六、虛方法表優化機制

1. 無虛方法表的查找成本

繼承鏈深度為 d,每次方法調用需向上遍歷最多 d 次查找 → 效能差。

2. 虛方法表結構

Child 類虛方法表:
+----------------+------------------+
| 方法簽名        | 實際程式碼地址    |
+----------------+------------------+
| action()       | Base.action()    |  ← 繼承自父類
| step()         | Child.step()     |  ← 覆寫
| toString()     | Object.toString()|  ← 繼承自 Object
| ...            | ...              |
+----------------+------------------+

Base 類虛方法表:
+----------------+------------------+
| action()       | Base.action()    |
| step()         | Base.step()      |
| toString()     | Object.toString()|
+----------------+------------------+

3. 優化效果

  • 類載入階段建立虛方法表(一次性成本)
  • 方法調用時:物件.虛方法表[方法索引] → O(1) 直接定位
  • 子類覆寫方法只在子類表中保留子類版本

七、變數存取:靜態綁定

1. 靜態變數存取

b.s  → 編譯器解析為 Base.s  → 輸出 1
c.s  → 編譯器解析為 Child.s → 輸出 10
  • 靜態變數不參與多態,編譯期根據引用宣告型別綁定
  • Base.s = 1,Child.s = 10,互不干擾

2. 實例變數存取

  • 同樣靜態綁定,根據引用宣告型別決定存取哪個欄位
  • 範例中 a 為 private,無法直接驗證
  • 若為 public:b.a 存取 Base.a,c.a 存取 Child.a

八、總結:繼承實現核心機制

機制核心原則關鍵點
類載入父優先,靜態初始化父→子靜態變數各自獨立,不共享
物件建構記憶體含所有父類欄位,初始化父→子構造器鏈隱式 super()
方法調用動態綁定(實際類型查找)虛方法表優化為 O(1)
變數存取靜態綁定(宣告類型決定)靜態/實例變數皆不多態

九、繼承的雙刃劍效應

  • 優點:程式碼復用、多態擴充、開放封閉原則
  • 缺點:
    • 破壞封裝:父類實現細節對子類可見
    • 強耦合:父類變更強制所有子類重新編譯/測試
    • 脆弱基類問題:子類依賴父類內部實現假設
    • 方法爆炸:深層繼承導致虛方法表膨脹
  • 最佳實踐:組合優於繼承、介面隔離、final 修飾不可變類、組合+委派模式

深入理解繼承的 JVM 級實現,能幫助開發者做出更好的架構決策:在正確的地方使用繼承,在更多地方選擇組合。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved