異常處理是 Java 程式設計的核心議題,幾乎所有入門課程都會涵蓋。然而,許多開發者僅停留在「知道有 try-catch」的層面,缺乏系統性的策略與最佳實務。本文將異常處理品質劃分為三個層次——好(Good)、壞(Bad)、糟(Ugly),並提供具體改善技巧。
Java 透過 throws 關鍵字在方法簽名中宣告可能拋出的異常,強制呼叫端處理或繼續拋出。例如 FileInputStream 建構子:
public FileInputStream(String name) throws FileNotFoundException
這種設計將錯誤條件納入方法契約,大幅提升程式碼可靠度與可維護性。
try {
FileInputStream fis = new FileInputStream(args[0]);
// 其他處理邏輯...
} catch (FileNotFoundException fnfe) {
System.out.println("File: " + args[0] + " not found. Aborting.");
System.exit(1);
}
Exception(非 RuntimeException)的類別,編譯器強制處理。Exception 封裝業務邏輯專屬錯誤資訊。繼承 RuntimeException 可繞過編譯器檢查,雖然方便但隱藏錯誤契約。NullPointerException、ArrayStoreException 屬此類。專業團隊應嚴格限制其使用場合。
try {
// 可能拋出多種檢查式異常的程式碼
} catch (Throwable t) {
t.printStackTrace(); // 僅印堆疊,未實質處理
}
這種寫法吞沒所有異常,掩蓋真實錯誤,僅適合快速原型或搭配中央錯誤處理器的頂層攔截,嚴禁用於核心業務邏輯。
記憶體不足(OutOfMemoryError)、寫入失敗、演算法錯誤等嚴重異常,往往難以優雅恢復。常見對策:重試迴路、觸發 GC、通知使用者、優雅降級服務。
throw new OutOfMemoryError() 本身需要記憶體分配,在記憶體耗盡時可能導致 JVM 崩潰或無法正確拋出。這是「伺候語言」而非「語言為我所用」的經典案例。
OutOfMemoryError 繼承自 Error 而非 Exception,屬非檢查式,但實際上是相當常見的資源耗盡情況。任何記憶體敏感程式都應處理,卻無法透過 throws 宣告強制處理。
setjmp/longjmp 可在處理完異常後返回原呼叫點繼續執行,Java 缺乏對應機制(如假設的 resume 關鍵字)。set_new_handler() 設定全域記憶體分配失敗處理器,Java 缺乏同等機制,導致 OutOfMemoryError 難以統一攔截。若您重視這些特性,可至 Java Developer Connection 提交建議。
catch (Throwable) 或 catch (Exception) 不加區分地吞沒錯誤。try-with-resources、多重 catch、JDK 14 的 Helpful NullPointerExceptions),持續改善異常處理品質。