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

Java 物件初始化順序深度解析:從原始碼到實際輸出

     編輯:Java 相關知識

現象重現:令人困惑的 null 輸出

觀察以下三個類別,預測 Lower.main() 輸出:

public class Upper {
    String upperString;
    public Upper() { Initializer.initialize(this); }
}

public class Lower extends Upper {
    String lowerString = null; // 關鍵行:顯式賦值 null
    public Lower() {
        super();
        System.out.println("Upper: " + upperString);
        System.out.println("Lower: " + lowerString);
    }
    public static void main(String[] args) { new Lower(); }
}

public class Initializer {
    static void initialize(Upper anUpper) {
        if (anUpper instanceof Lower) {
            Lower lower = (Lower) anUpper;
            lower.lowerString = "lowerInited"; // 父建構子中修改子欄位
        }
        anUpper.upperString = "upperInited";
    }
}

實際輸出:

Upper: upperInited
Lower: null

若移除 String lowerString = null; 的顯式賦值,輸出變為:

Upper: upperInited
Lower: lowerInited

根因:JLS §12.5 物件初始化精確順序

  1. 分配記憶體:所有欄位賦予預設值(null/0/false)。
  2. 父類建構子:super() 顯式或隱式調用,遞歸執行至 Object。
  3. 父類實例初始化器與欄位顯式賦值:按聲明順序執行。
  4. 父類建構子本體:執行 Upper() 內邏輯(此處呼叫 Initializer.initialize,修改了子類欄位)。
  5. 子類實例初始化器與欄位顯式賦值:關鍵點——此時若有 lowerString = null,會覆蓋 步驟 4 中設定的值!
  6. 子類建構子本體:執行 Lower() 內邏輯(列印)。

圖解時序

new Lower()
  │
  ├─ 1. 記憶體分配:upperString=null, lowerString=null
  │
  ├─ 2. super() → Upper()
  │     ├─ 3. Upper 欄位初始化(無)
  │     ├─ 4. Upper() 本體 → Initializer.initialize(this)
  │     │       ├─ upperString = "upperInited"
  │     │       └─ lowerString = "lowerInited"  (子類欄位被提前修改)
  │     └─ Upper() 結束
  │
  ├─ 5. Lower 顯式賦值:lowerString = null  ← 覆蓋!
  │
  └─ 6. Lower() 本體:列印 upperString=upperInited, lowerString=null

核心教訓

  • 永遠不要在父類建構子中調用可被覆寫的方法(或傳遞 this 給外部操作),因為此時子類欄位尚未完成初始化。
  • 顯式賦值 null 等同於「重置」,會抹除父建構子階段對子類欄位的任何修改。
  • 設計原則:初始化邏輯自下而上封裝,子類自行負責自己的欄位初始化,必要時提供 protected init() 模板方法供子類覆寫,而非在父建構子中強行操作子類狀態。

正確重構範例

public class Upper {
    String upperString;
    public Upper() { initUpper(); }
    protected void initUpper() { upperString = "upperInited"; }
}

public class Lower extends Upper {
    String lowerString; // 無顯式 null
    public Lower() {
        super(); // 父建構子完成後,再初始化自己
        lowerString = "lowerInited";
        System.out.println("Upper: " + upperString);
        System.out.println("Lower: " + lowerString);
    }
}

總結

Java 物件初始化順序是語言規範的硬性約束。深入理解「預設值 → 父建構子 → 子欄位顯式賦值 → 子建構子」這條鏈路,能避開無數隱蔽 Bug,寫出健壯的建構邏輯。

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