Java 類初始化過程看似簡單,實則暗藏玄機。搞不清順序會導致難以排查的 Bug(如靜態字段未初始化就被訪問、父子類初始化交織錯亂)。本文結合 JVM 規範與實戰代碼,徹底梳理完整初始化鏈路。
this(...),則先執行目標構造器,再回執行當前構造器剩餘語句。當創建子類實例時,順序為:
核心規則:父類初始化完全結束後,才開始子類實例初始化。靜態初始化按類加載順序(父→子)僅執行一次。
class FirstClass {
FirstClass(int i) { System.out.println("FirstClass(" + i + ")"); }
void useMethod(int k) { System.out.println("useMethod(" + k + ")"); }
}
class SecondClass {
static FirstClass fc1 = new FirstClass(1); // 1. 父類靜態字段
FirstClass fc3 = new FirstClass(3); // 5. 父類實例字段
static { // 2. 父類靜態塊
FirstClass fc2 = new FirstClass(2);
}
{ // 6. 父類實例塊
System.out.println("SecondClass's instance block");
}
SecondClass() { // 7. 父類構造器
System.out.println("SecondClass()");
}
FirstClass fc4 = new FirstClass(4); // 8. 父類實例字段(構造器後)
}
public class InitiationDemo {
SecondClass sc1 = new SecondClass(); // 9. 子類實例字段
{ // 10. 子類實例塊
System.out.println("Hello Java World!");
}
public static void main(String[] args) { // 3. 子類靜態初始化入口
System.out.println("Inside main()");
SecondClass.fc1.useMethod(100);
InitiationDemo idObj = new InitiationDemo();// 觸發子類實例初始化
}
static SecondClass sc2 = new SecondClass(); // 4. 子類靜態字段
}
FirstClass(1) ← 父類靜態字段 fc1
FirstClass(2) ← 父類靜態塊 fc2
FirstClass(3) ← 父類實例字段 fc3(sc2 初始化時)
SecondClass's instance block
FirstClass(4) ← 父類實例字段 fc4
SecondClass() ← 父類構造器(sc2 完成)
Inside main() ← main 入口
useMethod(100) ← 靜態方法調用
FirstClass(3) ← 子類實例字段 fc3(idObj 初始化時)
SecondClass's instance block
FirstClass(4) ← 子類實例字段 fc4
SecondClass() ← 父類構造器(idObj 觸發)
Hello Java World!← 子類實例塊
關鍵觀察點:
main 前完成,且僅執行一次。sc2 觸發父類實例初始化(3-7),但子類 InitiationDemo 的實例字段 sc1 尚未初始化。new InitiationDemo() 再次觸發父類實例初始化(因繼承),然後執行子類實例字段/塊/構造器。static final 常量屬於靜態初始化。實則編譯期常量(基本類型/String)在編譯期內聯,不觸發類初始化。NullPointerException。static {} 或單例懶加載;避免構造器中調用可覆寫方法(多態調用未初始化的子類字段)。Java 初始化順序遵循「靜態先於實例、父類先於子類、聲明順序執行」三大鐵律。深入理解此機制,能幫助你設計出更健壯的類結構,避開初始化陷阱。建議在複雜繼承體系中繪製初始化時序圖,作為代碼審查的標準依據。