final 三大應用層面
- final 變數:唯一賦值,基本型態值不可變、參考型態引用不可變(但物件內容可變)
- final 方法:禁止子類別覆寫,確保行為穩定
- final 類別:禁止繼承,所有方法隱式 final
final 成員變數:初始化規則與陷阱
核心規則
- 必須顯式初始化(無預設值)
- 僅能初始化一次:宣告處 或 建構子中(二擇一)
- 靜態 final:類載入時初始化;非靜態 final:實例化時初始化
程式碼驗證
public class Bat {
final double PI = 3.14; // 宣告處初始化
final int i; // 建構子初始化
final List<Bat> list; // 建構子初始化
Bat() { i = 100; list = new LinkedList<>(); }
Bat(int ii, List<Bat> l) { i = ii; list = l; }
public static void main(String[] args) {
Bat b = new Bat();
b.list.add(new Bat()); // OK:修改物件內容
// b.i = 25; // 編譯錯誤:不可重新賦值
// b.list = new ArrayList<>(); // 編譯錯誤:不可改變引用
b = new Bat(23, new ArrayList<>()); // OK:變數 b 本身可重新指向新物件
}
}
建構子中 try-catch 初始化陷阱
// 錯誤:編譯器認為 final 欄位可能已在 try 區塊賦值
public class Foo {
private final Thingie thingie;
public Foo() {
try { thingie = new Thingie(); }
catch (Exception e) { thingie = Thingie.getDefaultThingie(); } // Error
}
}
// 正確:使用區域變數暫存,最後一次性賦值給 final 欄位
public class Foo {
private final Thingie thingie;
public Foo() {
Thingie temp;
try { temp = new Thingie(); }
catch (Exception e) { temp = Thingie.getDefaultThingie(); }
thingie = temp; // 唯一賦值點
}
}
final 參考型態的「不可變」邊界
| 宣告 | 引用可變? | 物件內容可變? | 範例 |
| final Value v | 否 | 是 | v.i++ 合法;v = new Value() 非法 |
| final int[] arr | 否 | 是 | arr[0]++ 合法;arr = new int[3] 非法 |
| final List list | 否 | 是 | list.add() 合法;list = new ArrayList() 非法 |
結論:final 保護的是「引用不變」,非「物件凍結」。Java 無原生不可變物件機制。
解決 final 陣列/集合洩漏:防禦性封裝
危險寫法
public class DangerousStates {
private final String[] states = {"Alabama", "Alaska"};
public String[] getStates() { return states; } // 內部陣列直接暴露,外部可修改內容
}
安全寫法:回傳不可變視圖
public class SafeStates {
private final String[] states = {"Alabama", "Alaska"};
private final List<String> statesView = new AbstractList<>() {
public String get(int n) { return states[n]; }
public int size() { return states.length; }
};
public List<String> getStates() { return statesView; } // 不可變 List 視圖
// 或使用 Collections.unmodifiableList(Arrays.asList(states))
}
final 方法與類別:設計意圖
- final 方法:鎖定演算法/協議實作(如單例 getInstance、模板方法骨架)、效能考量(JIT 內聯優化)、安全敏感操作
- private/static 方法隱式 final:不可被覆寫(private 不可見、static 屬類不屬實例)
- final 類別:
String, Integer, Math 等不可變/工具類,防止繼承破壞語意
- 防止內部相依性破壞:類 X 依賴方法 M 特定行為,final X 或 final M 防止子類別改寫 M 導致 X 失效
final 參數與區域變數:特殊用法
- final 參數:基本型態無實質意義(傳值);參考型態防止方法內意外修改呼叫端物件
- 匿名內部類/區域內部類捕獲變數:被捕獲的區域變數/參數必須為 final(Java 8+ 實際上要求 effectively final)
void innerClass(final String str) {
class IClass { IClass() { System.out.println(str); } }
new IClass();
}
static final 常數最佳實踐
private final int i4 = rand.nextInt(20); // 實例常數:每實例不同
static final int i5 = rand.nextInt(20); // 類常數:全域共享,類載入時單次初始化
public static final int CONSTANT = 999; // 標準公開常數:命名全大寫+底線
驗證:建立多個實例,i4 各異,i5 相同 — 區分實例級與類級常數語意。
總結:final 使用決策樹
- 需要不可變引用?→ final 變數
- 需要禁止覆寫/繼承?→ final 方法/類別
- 需公開常數?→ public static final
- 需防禦性封裝可變內部狀態?→ final + 不可變視圖/防禦性複製
- 匿名內部類捕獲區域變數?→ final (effectively final)
善用 final 表達設計意圖、啟用編譯器檢查、輔助 JIT 優化,但切記:final 非萬靈丹,物件真正不可變需靠設計而非關鍵字。
- 上一篇文章: J2ME 專案實戰:MVC 模式在記事本應用中的完整應用(上)
- 下一篇文章: JDBC 獲取新增記錄自增主鍵的完整指南
Copyright ©
程式碼之家 All Rights Reserved