在 Java 程式設計中,final、finally 和 finalize 是三個發音相似但功能截然不同的關鍵字/機制。初學者極易混淆,資深開發者若不深入理解其底層運作原理,也可能在異常處理、資源釋放或效能優化上踩坑。本文將從語法語義、JVM 行為、最佳實踐三個維度,系統梳理這三者的核心差異。
當 final 修飾基本型別變數時,其值在初始化後不可更改;修飾物件引用時,引用地址不可變,但物件內部狀態仍可修改。初始化位置僅限「宣告處」或「建構子中」二選一。
public class FinalDemo {
final int CONST_VAL = 10; // 宣告時初始化
final StringBuffer buffer; // 僅宣告
public FinalDemo() {
buffer = new StringBuffer(); // 建構子初始化
}
}
基本型別參數本身是值傳遞,加 final 意義不大;但對物件引用參數加 final,可避免方法內部將參數指向新物件,導致呼叫端引用異常。此外,匿名內部類捕獲區域變數時,該變數必須為 final(或實際上不可變)。
String)。finally 區塊在 try 區塊正常結束、拋出異常、或遇到 return/break/continue 時一定會執行(除非 JVM 退出、線程被殺、斷電等極端情況)。這使其成為釋放非記憶體資源(檔案句柄、資料庫連線、鎖)的最佳位置。
// 無 finally:碼冗長且易漏關資源
public void writeNoFinally(String path, String data) throws IOException {
FileWriter fw = new FileWriter(path);
try {
fw.write(data);
} catch (IOException e) {
fw.close(); // 重複關閉
throw e;
}
fw.close(); // 正常路徑關閉
}
// 有 finally:邏輯集中、無漏關風險
public void writeWithFinally(String path, String data) throws IOException {
FileWriter fw = new FileWriter(path);
try {
fw.write(data);
} finally {
fw.close(); // 統一釋放
}
}
public int testFinallyReturn() {
try {
return 1;
} finally {
return 2; // 實際返回 2,try 的返回值被「吞沒」
}
}
原理:JVM 會將 try 的返回值暫存到局部變數表,執行 finally 後再恢復;若 finally 有 return,則直接以新值返回,原值丟棄。嚴禁在 finally 中使用 return/throw。
Object,GC 回收物件前可能呼叫一次。@Deprecated,Java 18 移除(JEP 421)。// Java 7+ 自動資源管理(ARM)
try (FileWriter fw = new FileWriter(path)) {
fw.write(data);
} // 編譯器自動生成 finally 關閉
// Java 9+ Cleaner API(輕量級替代 finalize)
Cleaner cleaner = Cleaner.create();
Cleaner.Cleanable cleanable = cleaner.register(obj, () -> releaseResource());
| 關鍵字/機制 | 類別 | 核心用途 | 關鍵注意點 |
|---|---|---|---|
| final | 修飾符 | 不可變約束、禁止繼承/覆寫、編譯器優化 | 引用不變 ≠ 物件不變;慎用 final 方法內聯 |
| finally | 區塊 | 確定性資源釋放 | 幾乎必執行;禁用 return/throw 覆蓋控制流 |
| finalize | 方法 | (舊)GC 前清理 | 不可靠、已移除;請用 try-with-resources / Cleaner |
final 類/欄位/方法。try-with-resources 或 finally 統一 close()。finally 中寫 return、throw、break、continue。finalize() 覆寫 → 立即遷移至 Cleaner 或 AutoCloseable。深入理解這三者,不僅能寫出更健壯的 Java 代碼,更能在面試、Code Review、疑難雜症排查中游刃有餘。建議收藏對照表,遇到疑惑隨時查閱。