在 Java 語言中,關於何時使用檢查型異常與非檢查型異常的爭論由來已久。傳統觀點(Sun 官方教程、《Effective Java》)主張優先使用檢查型異常,但近年來 Bruce Eckel、Rod Johnson 等專家提出質疑,認為非檢查型異常在大型項目中更具優勢。C# 甚至直接取消了檢查型異常。本文深入分析雙方觀點,探討最佳實踐。
Exception(非 RuntimeException),編譯器強制要求在方法簽名中聲明 throws 或以 try-catch 捕獲。代表預期可恢復的錯誤(如 IO 錯誤、資料庫連接失敗)。RuntimeException,編譯器不強制處理。代表編程錯誤(如空指針、數組越界、除零)或不可恢復的系統級故障。Sun 教程與 Josh Bloch (Effective Java) 的核心建議:
方法簽名拋出 SQLException、IOException 等底層異常,違反抽象原則。調用者不關心數據庫怎麼壞了,只關心「用戶不存在」。解決方案:異常轉換與鏈,拋出 NoSuchUserException 並保留根因堆疊。
實現變更導致 throws 子句頻繁修改,波及所有調用者。極端情況下開發者乾脆聲明 throws Exception,喪失異常文檔化價值。
大量 try-catch 塊(僅記錄或重拋)淹沒業務邏輯,使代碼難以閱讀。
空 catch 塊或過度寬泛的 catch (Exception e) 導致異常悄無聲息地消失,甚至誤捕獲 RuntimeException 阻礙錯誤傳播。
多層捕獲-包裝-重拋使堆疊跟蹤極長(JDK 1.4+ 異常鏈緩解但未根治)。
主張所有異常皆為非檢查型,提供 ExceptionAdapter 將檢查型異常包裝為 RuntimeException 並保留原始堆疊。觀點極端,社區褒貶不一。
《J2EE Design and Development》作者提出三類異常處理策略:
若選擇非檢查型異常,必須在 Javadoc 中嚴格文檔化 @throws,否則調用者無法知曉可能拋出的異常。C++ 經驗表明:缺乏編譯器強制,API 文檔往往嚴重缺失。非檢查型異常對文檔化要求更高,而非更低。
try-finally/try-with-resources:非檢查型異常環境下更需顯式資源清理。@throws 必須完整準確。沒有銀彈。檢查型異常提供編譯期安全網,非檢查型異常減少樣板代碼。關鍵在於:理解業務語義,選擇合適策略,並嚴守文檔化契約。