Java 類載入與初始化遵循嚴格規範,但許多開發者只知「main 先跑」,不知靜態塊、實例塊、建構子、父子類之間的精確順序。本文通過層層遞進的代碼實驗,完整還原 JVM 初始化時序,並揭示「無 main 也能跑」的冷知識。
package → 隱式屬於「無名包」import java.lang.* → String、Math、Integer 等直接用extends → 隱式 extends Objectpublic class JRun1 {
static { System.out.println("static{}"); }
{ System.out.println("{}"); }
public JRun1() { System.out.println("建構子"); }
public static void main(String[] args) { System.out.println("main()"); }
}
static{}
main()
結論:靜態塊 → main()。實例塊、建構子因無 new 不執行。
冷知識:無 main 也能執行
public class NoMain {
static { System.out.println("no main()"); System.exit(0); }
}
類載入時執行靜態塊,System.exit(0) 正常結束。
實驗 2:main 中建立本類物件
public static void main(String[] args) {
System.out.println("main()");
new JRun1();
}
輸出
static{}
main()
{}
建構子
結論:靜態塊 → main → 實例塊 → 建構子。
階段三:繼承鏈初始化鐵律
class Father {
Father() { System.out.println("父建構子"); }
static { System.out.println("父靜態塊"); }
{ System.out.println("父實例塊"); }
}
public class Son extends Father {
Son() { System.out.println("子建構子"); }
static { System.out.println("子靜態塊"); }
{ System.out.println("子實例塊"); }
public static void main(String[] args) { new Son(); }
}
輸出
父靜態塊
子靜態塊
父實例塊
父建構子
子實例塊
子建構子
總結:完整初始化順序口訣
父靜態 → 子靜態 → 父實例塊 → 父建構子 → 子實例塊 → 子建構子
關鍵規則:
- 靜態塊:類載入時執行,父先子後,僅一次
- 實例塊:每次
new 執行,在建構子之前,父先子後
- 建構子:
super() (隱式/顯式) → 實例塊 → 本建構子碼
- 實例塊與建構子緊貼執行,不會父子交替
進階陷阱:靜態欄位初始化順序
class Test {
static int a = 1;
static { a = 2; }
static int b = a + 1; // b = 3
}
靜態欄位與靜態塊按源碼順序執行。
實戰意義
- 單例模式靜態內部類寫法利用「類載入時機」保證線程安全
- 靜態塊異常會導致
ExceptionInInitializerError,類永不可用
- 父類靜態塊拋異常,子類永無法載入
- 理解順序才能正確設計「註冊中心」、「驅動載入」等靜態初始化邏輯
結語
Java 初始化機制看似簡單,實則暗合 JVM 類載入規範 (JVMS §5.5)。掌握「父靜→子靜→父實→父構→子實→子構」鐵律,不僅能避開面試陷阱,更能在框架設計、靜態資源管理、異常診斷中做出正確判斷。下次見到靜態塊,請敬畏地說一聲:「妳是第一個跑的。」