Java 異常機制獨創檢查型異常,編譯器強制聲明或捕獲。早期正統觀點(Sun 教程、《Effective Java》)推崇「優先用檢查型異常表示可恢復錯誤,運行時異常僅用於編程錯誤」。但近年 Bruce Eckel、Rod Johnson 等大佬公開反水,C# 更直接取消檢查型異常。本文梳理雙方核心論點,助你在項目中做出理性選擇。
throws IOException 明確告知調用者「可能失敗且你應處理」Bloch 《Effective Java》四大鐵律:
DAO 方法拋 SQLException,Service 層被迫捕獲或繼續拋,導致上層業務代碼耦合底層存儲技術。違反「拋出與抽象匹配」原則。
接口新增一個檢查型異常,所有實現類、所有調用鏈必須修改簽名。接口演化極其痛苦。
try {
riskyCall();
} catch (CheckedException e) {
// 沒辦法,只能印日誌或包裝成 RuntimeException 拋出
throw new RuntimeException(e);
}
大量樣板代碼,真正錯誤處理邏輯反而被淹沒。
函數式接口不拋檢查型異常,迫使開發者寫「嘗試-捕獲-包裝」工具方法,代碼噪音大增。
有人用檢查型異常實現業務分支(如 throw new InsufficientFundsException() 代替返回狀態),濫用異常機制。
| 場景 | 推薦策略 |
|---|---|
| 底層資源訪問 | 捕獲具體檢查型異常,包裝為業務運行時異常拋出(異常鏈保留根因) |
| 業務邏輯可恢復錯誤 | 定義領域專用運行時異常(如 InsufficientBalanceException extends RuntimeException),攜帶豐富業務上下文 |
| 編程錯誤/前置條件違反 | 直接拋 IllegalArgumentException、NullPointerException 等標準運行時異常 |
| 庫/框架 SPI 接口 | 謹慎使用檢查型異常定義擴展點,提供默認實現降低實現者負擔 |
OrderNotFoundException、PaymentDeclinedException),攜帶業務關鍵數據(訂單 ID、錯誤代碼)。throw new BizException("msg", cause),保留完整堆疊追蹤。@throws 描述可能拋出的業務異常,而非靠編譯器強制。結論:檢查型異常在 JDK 核心庫、IO、反射等底層 API 有其歷史合理性;但在業務應用層,領域運行時異常 + 統一處理機制,才是更高效、可維護的選擇。