final 變量一旦賦值不可更改,但初始化位置與時機有嚴格規則。本文系統梳理三大初始化位置、執行順序、編譯器檢查邏輯及常見坑點。
final int a = 1;{ a = 2; } 或 static { b = 2; }public Demo() { c = 3; }注意:Java 1.1 前僅支持聲明處初始化;靜態 final 變量不能在構造器初始化(構造器屬實例,靜態屬類)。
public class InitOrder {
// 實例 final
final int a1 = 1; // 1. 聲明處
final int b1; // 2. 構造器
final int c1; // 3. 實例塊(後)
final int d1; // 4. 實例塊(前) - 特殊
// 靜態 final
static final int a2 = 1;
static final int c2; // 靜態塊(後)
static final int d2; // 靜態塊(前) - 特殊
// 實例初始化塊(前)
{ System.out.println("before-field"); d1 = 3; }
// 靜態初始化塊(前)
static { System.out.println("before-static"); d2 = 3; }
// 實例初始化塊(後)
{ System.out.println("after-field"); c1 = 4; System.out.println("c1=" + c1); }
// 靜態初始化塊(後)
static { System.out.println("after-static"); c2 = 4; System.out.println("c2=" + c2); }
public InitOrder() { b1 = 2; }
public static void main(String[] args) {
InitOrder o = new InitOrder();
System.out.println("c1=" + o.c1);
System.out.println("c2=" + o.c2);
System.out.println("d1=" + o.d1);
System.out.println("d2=" + o.d2);
}
}
before-static
after-static
c2=4
before-field
after-field
c1=4
c1=4
c2=4
d1=3
d2=3
| 場景 | 規則 | 說明 |
|---|---|---|
| 聲明處初始化 | ✅ 總是合法 | 編譯期常量折疊優化 |
| 構造器初始化實例 final | ✅ 必須在所有構造器中賦值 | 編譯器強制檢查 |
| 構造器初始化靜態 final | ❌ 編譯錯誤 | 靜態屬類,構造器屬實例 |
| 實例塊前初始化後定義的 final | ✅ 賦值合法,讀取非法 | d1=3 OK,print(d1) 編譯錯誤 |
| 靜態塊前初始化後定義的 static final | ✅ 賦值合法,讀取非法 | 同理,d2=3 OK,print(d2) 編譯錯誤 |
| 靜態塊初始化實例 final | ❌ 編譯錯誤 | 靜態階段實例 final 尚未分配 |
| 實例塊初始化靜態 final | ❌ 編譯錯誤 | 實例階段晚於靜態階段 |
Java 要求「讀取前必須確定賦值」。編譯器按文本順序進行確定性分析:
{ d1 = 3; print(d1); } → d1 在 print 前已賦值 → ✅{ print(d1); d1 = 3; } → print 時 d1 未確定賦值 → ❌這與運行時內存分配無關,純屬編譯期靜態分析規則。
static final int MAX = 100;,命名全大寫下劃線final 初始化遵循「聲明處 > 初始化塊 > 構造器」的靈活性,但受「靜態先於實例、文本順序確定賦值」兩大硬約束。理解編譯器確定性賦值分析,能避開 90% 的 final 初始化坑點。