final 是 Java 中最強大卻常被誤解的關鍵字之一。它不僅修飾「常量」,更是不可變性設計、線程安全、編譯器優化、API 契約約束的核心工具。本文通過代碼實驗、內存模型分析、設計模式視角,系統梳理 final 在變量、方法、類三個維度的語義與實戰智慧。
final int NUM = 10;
NUM = 20; // 編譯錯誤: cannot assign a value to final variable
基本類型變量直接存儲值,final 確保賦值後值永不改變。編譯器將其視為編譯期常量 (Constant Variable),直接內聯到字節碼。
final StringBuilder sb = new StringBuilder("hello");
sb.append(" world"); // ✅ 合法:修改對象內容
// sb = new StringBuilder(); // ❌ 編譯錯誤:改變引用指向
核心結論:final 限制的是變量槽 (Slot) 存儲的引用地址不變,而非堆上對象的內部狀態。這是初學者最易混淆的點。
class A { int t = 10; void setT(int v) { t = v; } int getT() { return t; } }
final A a = new A();
a.setT(29); // ✅ 合法:修改對象屬性
System.out.println(a.getT()); // 輸出 29
// A b = new A();
// a = b; // ❌ 編譯錯誤:不能指向另一個對象
class Demo {
final int x; // 宣告時不賦值
final String s;
Demo(int x) { this.x = x; } // 必須在所有構造函數中初始化
Demo(String s) { this.s = s; }
{ x = 100; } // 實例初始化塊也可初始化
static { /* 靜態 final 需在靜態塊初始化 */ }
}
規則:final 變量必須在構造函數完成前 (實例變量) 或 類初始化完成前 (靜態變量) 確定賦值,否則編譯報錯。
final 方法不能被子類重寫 (Override)。final 方法可以被繼承,子類可直接調用 super.finalMethod()。final 方法可以被重載 (Overload),同類中可定義同名不同參數列表的方法。| 修飾符 | 可繼承 | 可重寫 | 可重載 | 子類可見性 | 典型用途 |
|---|---|---|---|---|---|
| final | ✅ | ❌ | ✅ | ✅ | 鎖定核心算法、模板方法骨架 |
| private | ❌ | ❌ (隱藏) | ✅ (同類內) | ❌ | 內部實現細節、封裝 |
| static | ✅ (隱藏) | ❌ (隱藏) | ✅ | ✅ | 類級行為、工具方法 |
class Base {
public final void finalMethod() { System.out.println("Base final"); }
private void privateMethod() { System.out.println("Base private"); }
private final void privateFinal() { System.out.println("Base private final"); }
}
class Derived extends Base {
// public final void finalMethod() {} // ❌ 編譯錯誤:不能重寫 final 方法
private void privateMethod() { System.out.println("Derived private"); } // ✅ 合法:新方法,非重寫
private final void privateFinal() { System.out.println("Derived private final"); } // ✅ 合法
}
設計啟示:final 方法是模板方法模式的關鍵——父類定義算法骨架 (final),子類實作具體步驟 (abstract 或可重寫方法),確保流程不可被竄改。
final class 不能被繼承,所有方法隱式 final。String、Integer、LocalDate 等所有包裝類與值類型。String 不可變。若可繼承,惡意子類可竄改行為 (如 equals()、hashCode()) 導致安全漏洞。hashCode() 緩存、substring() 共享底層字符數組 (JDK 6/7 差異) 等優化建立在不可變前提上。寫入 final 域的操作 不能重排序 到構造函數之外;讀取 final 域的操作 不能重排序 到對象引用賦值之前。
class FinalFieldExample {
final int x; int y;
static FinalFieldExample obj;
FinalFieldExample() {
x = 3; // 1. 寫 final 域
y = 4; // 2. 寫普通域
}
static void writer() { obj = new FinalFieldExample(); } // 3. 發布引用
static void reader() {
FinalFieldExample o = obj; // 4. 讀引用
if (o != null) {
int a = o.x; // 5. 讀 final 域 (保證看到 3)
int b = o.y; // 6. 讀普通域 (可能看到 0 或 4)
}
}
}
保證:線程 B 通過 obj 看到對象時,必定能看到 x=3 (final 域正確值);但 y 可能看到 0 (構造函數寫入未可見)。這是 final 域在無鎖發布不可變對象時的關鍵安全網。
public final class ImmutablePoint {
private final int x, y; // final 域
public ImmutablePoint(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
// 無 setter、無可變方法、類 final
}
// 多線程環境下,只要引用發布安全 (volatile/AtomicReference/final 域/鎖內發布)
// 即可無鎖安全共享,極大降低同步複雜度。
final List list = new ArrayList<>();
list.add("a"); // ✅ 合法!final 只保護 list 引用,不保護內容
// list = new ArrayList<>(); // ❌ 編譯錯誤
正解:使用 Collections.unmodifiableList()、List.of() (JDK 9+)、Guava ImmutableList 實現真正不可變集合。
void modify(final List list) {
list.add("x"); // ✅ 合法!final 只防止 list 指向新對象
}
參數 final 僅限制方法棧幀內的局部變量槽,對調用者無影響。
static final 常量全大寫下劃線 MAX_CONNECTIONS。final class + final 域 + 無 setter。final 域 = 無鎖線程安全,優先選擇。final,防止子類破壞契約 (如 Collections.sort() 內部調用的 List.sort() 為 default 非 final,但核心算法鎖定)。final 看似簡單,實則貫穿 Java 語言設計的核心哲學——通過編譯器約束不可變性,換取運行時的安全、性能與簡潔。從基本類型常量到引用穩定,從模板方法鎖定到不可變對象發布,final 是每個 Java 開發者必須精通的利器。下次敲下 final,請記住:你在向編譯器、JVM、未來的維護者宣告——「這裡的值/行為/結構,永不妥協」。