《Core Java》等經典教材常只講建構子鏈,卻忽略「靜態區塊、實例區塊、建構子、繼承鏈」交織的完整順序。實戰中初始化順序錯誤會導致難以復現的 Bug。本文結合位元碼視角,梳理出一套「不背誦、能推導」的完整規則。
核心原則:<clinit>()(類初始化)先於 <init>()(對象初始化);父類先於子類;靜態先於實例;代碼順序決定同層級順序。
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); // ① 靜態欄位
FirstClass fc3 = new FirstClass(3); // ② 實例欄位
static { // ③ 靜態區塊
FirstClass fc2 = new FirstClass(2);
}
{ // ④ 實例區塊
System.out.println("SecondClass's instance block");
}
SecondClass() { // ⑤ 建構子
System.out.println("SecondClass()");
}
FirstClass fc4 = new FirstClass(4); // ⑥ 實例欄位
}
public class InitiationDemo {
SecondClass sc1 = new SecondClass(); // ⑦ 實例欄位
{ // ⑧ 實例區塊
System.out.println("Hello Java World!");
}
public static void main(String[] args) { // ⑨ main 入口
System.out.println("Inside main()");
SecondClass.fc1.useMethod(100); // ⑩ 訪問靜態欄位
InitiationDemo idObj = new InitiationDemo(); // ⑪ new 本類
}
static SecondClass sc2 = new SecondClass(); // ⑫ 靜態欄位
}
FirstClass(1) ← ⑫ 觸發 SecondClass 類初始化 → ①
FirstClass(2) ← ③ 靜態區塊
FirstClass(3) ← ⑪ new SecondClass() → 父類無靜態 → 子類實例欄位 ②
SecondClass's instance block ← ④ 實例區塊
FirstClass(4) ← ⑥ 實例欄位
SecondClass() ← ⑤ 建構子
Inside main() ← ⑨ main 開始
useMethod(100) ← ⑩
FirstClass(3) ← ⑪ new InitiationDemo() → new SecondClass() 再走一遍實例初始化
SecondClass's instance block
FirstClass(4)
SecondClass()
Hello Java World! ← ⑧ 實例區塊
static SecondClass sc2 = new SecondClass();InitiationDemo 的 <clinit>()<init>()static SecondClass sc2 → 觸發 SecondClass 的 <clinit>()SecondClass 無父類靜態 → 直接執行 ① ③main 方法執行時,類已初始化完畢SecondClass.fc1.useMethod(100) 直接使用已初始化好的靜態欄位,不再觸發初始化new InitiationDemo() 觸發本類 <init>()Object <init>()new SecondClass() → 完整走一遍 SecondClass 的實例初始化(②④⑥⑤)<init>() 結束| 誤區 | 真相 |
|---|---|
| 「靜態區塊在 main 之前執行」 | 靜態區塊在「類首次主動使用」時執行,不一定是 main 前 |
| 「實例區塊比建構子早」 | 實例區塊與實例欄位按代碼順序交織,統一在 super() 之後、建構子本體之前 |
| 「父類建構子執行前,子類實例欄位已初始化」 | 父類 <init>() 完全結束後,才輪到子類實例欄位/區塊 |
「this() 可寫在建構子任意位置」 | this(...) 必須是建構子第一行,否則編譯錯誤 |
javap -c -v YourClass.class
查看 <clinit>() 與 <init>() 的指令序列,順序一目了然。
初始化順序 = 類載入時機 + 父子層級 + 靜態優先 + 代碼順序。掌握這四個維度,任何複雜繼承鏈初始化都能手動推導,無需死記硬背。