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

深度剖析 Java 建構子調用順序:從 Think in Java 看物件初始化的隱藏陷阱

     編輯:Java 相關知識

前言:建構子並不像想像中那麼簡單

許多開發者以為 new 一個物件只是簡單的記憶體配置,但《Think in Java》作者指出:建構子的調用順序蘊含微妙機制,稍有不慎便會導致難以排查的 Bug。本文透過兩個經典案例,層層揭開物件初始化的真實面貌。

案例一:繼承鏈與成員初始化的標準順序

class Meal { Meal() { System.out.println("meal"); } }
class Bread { Bread() { System.out.println("Bread"); } }
class Cheese { Cheese() { System.out.println("Cheese"); } }
class Lettuce { Lettuce() { System.out.println("Lettuce"); } }
class Lunch extends Meal { Lunch() { System.out.println("Lunch"); } }
class PortableLunch extends Lunch { PortableLunch() { System.out.println("PortableLunch"); } }
public class Sandwich extends PortableLunch {
    private Bread b = new Bread();
    private Cheese c = new Cheese();
    private Lettuce l = new Lettuce();
    public Sandwich() { System.out.println("Sandwich"); }
    public static void main(String[] args) { new Sandwich(); }
}

輸出結果:

meal Lunch PortableLunch Bread Cheese Lettuce Sandwich

標準初始化四步驟

  1. 基類建構子遞歸調用:從繼承鏈頂層(Object)向下,依序執行各層建構子(meal → Lunch → PortableLunch)。
  2. 成員變數按宣告順序初始化:Bread → Cheese → Lettuce。
  3. 導出類建構子主體執行:Sandwich 建構子內的列印語句。
  4. 隱藏的第零步:記憶體空間二進位制清零(所有欄位預設值為 0/null/false)。

案例二:多型與建構子調用的致命陷阱

class Glyph {
    void draw() { System.out.println("Glyph draw()"); }
    Glyph() {
        System.out.println("Glyph before draw();");
        draw(); // 動態綁定!
        System.out.println("Glyph after draw();");
    }
}
class RoundGlyph extends Glyph {
    private int radius = 1;
    RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph(), radius:" + radius);
    }
    void draw() {
        System.out.println("RoundGlyph.draw(), radius:" + radius); // 此處印出 0!
    }
}
public class ConstructorTest2 {
    public static void main(String[] args) { new RoundGlyph(5); }
}

輸出結果:

Glyph before draw(); RoundGlyph.draw(), radius:0 Glyph after draw(); RoundGlyph(), radius:5

為什麼 radius 是 0?

關鍵在於 初始化順序:

  1. 記憶體清零 → radius = 0(預設值)
  2. 父類建構子執行 → 呼叫 draw()(動態綁定到子類覆寫方法)
  3. 子類成員初始化 → radius = 1(宣告處賦值)
  4. 子類建構子主體 → radius = 5(參數賦值)

當父類建構子呼叫 draw() 時,子類的 radius 尚未執行第 3 步初始化,仍為預設值 0。這是 在建構子中呼叫非 final/private 方法的經典陷阱。

核心準則:建構子內只調用 final 或 private 方法

這些方法無法被覆寫,避免動態綁定導致存取未初始化的成員。若無法避免,務必確保被調用方法不依賴子類尚未初始化的狀態。

總結

物件初始化實際包含四階段:記憶體清零 → 父類建構子遞歸 → 成員初始化 → 本類建構子主體。理解此機制,能有效規避多型與建構子交織產生的隱性 Bug,打牢 Java 進階開發的基石。

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