final 是 Java 中語義最豐富、誤解最多的關鍵字之一。它可修飾類、方法、變量(字段/局部變量/參數),每種場景語義不同:類不可繼承、方法不可覆寫、變量不可重賦值(引用不可變,非對象內容不可變)。本文系統梳理四大場景,結合 JMM 語義與編譯器優化,釐清邊界條件。
public final class ImmutableString { // String、Integer、LocalDate 等皆為 final
// ...
}
// 編譯錯誤:Cannot inherit from final class
class SubClass extends ImmutableString {}
abstract 共存public class Base {
public final void templateMethod() {
// 模板方法模式:固定流程,子類只能通過 hook 方法擴展
step1();
hook(); // 可覆寫的擴展點
step2();
}
protected void hook() {} // 非 final,允許擴展
}
| 變量類型 | 初始化時機 | 語義 |
|---|---|---|
| 靜態字段 (static final) | 聲明時 / 靜態代碼塊 | 編譯期常量(基本類型/String)進入常量池,調用處直接內聯 |
| 實例字段 | 聲明時 / 實例初始化塊 / 構造器 | 構造器結束前必須賦值,JMM 保證構造完成前不可見(無需同步) |
| 局部變量 | 首次賦值前 | 僅限賦值一次,編譯器做定值分析優化 |
| 方法參數 | 調用時傳入 | 方法體內不可重賦值,防止誤修改參數引用 |
final List<String> list = new ArrayList<>();
list.add("hello"); // ✅ 合法:修改對象內部狀態
// list = new LinkedList<>(); // ❌ 編譯錯誤:引用不可重賦值
final User user = new User();
user.setName("Bob"); // ✅ 合法
// user = new User(); // ❌ 編譯錯誤
真正不可變需類本身設計為不可變(如 String、LocalDate、Guava ImmutableList)。
public class FinalFieldExample {
final int x; // final 域
int y; // 普通域
static FinalFieldExample obj;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() { obj = new FinalFieldExample(); }
static void reader() {
FinalFieldExample o = obj;
if (o != null) {
int a = o.x; // 保證讀到 3
int b = o.y; // 可能讀到 0(未構造完成就發布)
}
}
}
JMM 規則:寫 final 域的操作與將構造對象引用賦值給普通變量之間,禁止重排序。讀 final 域時,若對象引用可見,則 final 域必可見且為正確值。
static final int MAX = 100; 使用處直接替換為 100需要 final 嗎?
├─ 類:不想被繼承 / 安全敏感 / 不可變類 → final class
├─ 方法:模板方法固定流程 / 構造器調用 / 性能敏感熱點 → final method
├─ 字段:
│ ├─ 基本類型/String/枚舉:業務常量 → static final
│ ├─ 引用類型:引用不變但內容可變 → final(構造器初始化)
│ └─ 真正不可變對象:需類級不可變設計
└─ 參數/局部變量:防止誤重賦值 / 編譯器優化 → final