在 Java 程式設計中,static 關鍵字修飾的靜態變數初始化順序常讓開發者感到困惑。本文將從簡單規則切入,逐步深入到複雜的交叉引用場景,並結合經典代碼範例,徹底釐清靜態變數初始化的底層邏輯。
Java 靜態變數初始化遵循嚴格的兩階段過程:
public class Test {
public static Test1 t = new Test1();
public static int a = 0;
public static int b;
public static void main(String[] arg) {
System.out.println(Test.a); // 輸出 0
System.out.println(Test.b); // 輸出 1
}
}
class Test1 {
public Test1() {
Test.a++;
Test.b++;
}
}
執行流程解析:
Test 被主動使用(main 方法),觸發初始化。t 設為 null、a 設為 0、b 設為 0。t = new Test1(),構造器中 Test.a++ 使 a 變為 1,Test.b++ 使 b 變為 1。a = 0,將 a 重置為 0;b 無顯式賦值,保持 1。a=0、b=1。當初始化過程涉及多個類相互引用時,需遵循以下擴展規則:
A.b = B.a),則暫停 A 類初始化,轉而完整初始化 B 類(遵循上述兩階段規則),完成後返回 A 類繼續。new A(),會觸發 A 類實例構造器執行,此時 A 類靜態變數可能尚未完全初始化,構造器中讀取到的是當前的「中間狀態值」。new Integer(10)、Math.random())仍遵循運行時初始化順序。public class A {
public static int b = B.a; // ①
public static A plus = new A("A"); // ②
public static final int finalInt = (int)(Math.random()*100); // ③
public static B p = new B("A"); // ④
public static final String finalStr = "finalStr"; // ⑤
public static final Integer finalInteger = new Integer(10); // ⑥
public static int a = 1; // ⑦
public static B c = null; // ⑧
// ... 構造器與 main 方法省略
}
class B {
public static int t = A.a; // ⑨
public static A plus = new A("B"); // ⑩
public static int a = 1; // ⑪
// ... 構造器省略
}
class C {
public static final A a = new A("C"); // ⑫
}
關鍵執行節點追蹤:
main 執行,訪問 A.b → 觸發 A 類初始化。b = B.a → 需要 B.a,暫停 A,轉初始化 B 類。t = A.a → 讀取 A.a 當前值(0,因 A 類初始化暫停中,a 仍為默認值 0)。plus = new A("B") → 實例化 A,調用構造器,打印日誌顯示 A.b=0、A.finalInt=0、B.a=0、B.plus=null。a = 1 → B.a 變為 1。B 類初始化完成,返回 A 類。b = B.a = 1。執行 ② plus = new A("A") → 再次實例化 A,構造器打印 A.b=1、B.a=1、B.plus 指向第一個 A 實例。finalInt 賦隨機值(如 61)。執行 ④ p = new B("A") → 實例化 B,構造器打印 A.a=0(因 ⑦ 尚未執行)、A.p=null、A.finalInteger=null、A.finalStr="finalStr"(編譯期常量已賦值)。a=1、c=null。A 類初始化結束。main 打印 A.b=1、B.t=0。C.a → 觸發 C 類初始化 → ⑫ new A("C") → 第三次實例化 A,此時 A 類已初始化完畢,構造器讀取到最終值。類加載流程總結:
關鍵點:若不創建對象,實例初始化步驟(3-5)完全不會執行;靜態初始化(1-2)全程只執行一次。
將多條靜態初始化語句包裹在 static { ... } 中,本質上等同於分散聲明的多個靜態賦值語句,執行順序與聲明順序一致,僅在類首次被主動使用時執行一次。
實例初始化塊 { ... } 與構造器配合,每次 new 對象都會按順序執行:父類靜態 → 子類靜態 → 父類實例塊 → 父類構造器 → 子類實例塊 → 子類構造器。
static final 基本類型/String + 常量表達式)優先使用,編譯器會內聯優化。-XX:+TraceClassLoading 觀察類加載時機。掌握靜態初始化的精確規則,是寫出健壯、可預測 Java 代碼的基石之一。