類載入與初始化遵循嚴格順序,理解此機制對調試複雜繼承體系、單例模式、靜態工廠至關重要。
class Parent {
static String name = "hello";
static { System.out.println("1. parent static block"); }
{ System.out.println("3. parent instance block"); }
public Parent() { System.out.println("4. parent constructor"); }
}
class Child extends Parent {
static String childName = "hello";
static { System.out.println("2. child static block"); }
{ System.out.println("5. child instance block"); }
public Child() { System.out.println("6. child constructor"); }
}
public class InitOrderTest {
public static void main(String[] args) {
new Child(); // 唯一觸發點
}
}
/* 輸出:
1. parent static block
2. child static block
3. parent instance block
4. parent constructor
5. child instance block
6. child constructor
*/
| 階段 | 觸發時機 | 執行內容 | 順序 |
|---|---|---|---|
| 父類靜態初始化 | Child 類首次被主動使用 (new、靜態方法、靜態欄位) | 靜態欄位賦值、靜態塊 static {} | 1 |
| 子類靜態初始化 | 緊接父類靜態完成 | 靜態欄位賦值、靜態塊 | 2 |
| 父類實例初始化 | new Child() 分配記憶體後,隱式 super() 前 | 實例欄位賦值、實例塊 {} | 3 |
| 父類構造器 | 實例初始化完成 | 構造器代碼 | 4 |
| 子類實例初始化 | 父構造器返回後 | 實例欄位賦值、實例塊 | 5 |
| 子類構造器 | 最後 | 構造器代碼 | 6 |
new 都執行,編譯器將其複製到**每個構造器開頭**,super() 之前。super():子類構造器首行必須調用父構造器 (顯式或隱式),構成鏈式調用。
class Trap {
static int i = 10;
static { i = 20; }
static int j = i; // j = 20
}
class Sub extends Trap {
static { System.out.println(i); } // 輸出 20 (父類已初始化完成)
}
NoClassDefFoundError (包裝原始 ExceptionInInitializerError)。