為什麼需要異常處理?
現實世界充滿不確定:檔案不存在、網路斷開、記憶體耗盡、使用者輸入非法… 若無統一機制,錯誤處理碎片化、易遺漏、難維護。Java 引入異常物件化機制:
- 將錯誤封裝為物件,攜帶類型、訊息、堆疊追蹤、原因鏈。
- 支援自動傳遞:呼叫鏈向上冒泡,直到被捕獲或進入主線程終止。
- 實現關注點分離:正常邏輯與錯誤處理解耦,代碼更清晰。
異常體系全景圖
java.lang.Throwable (所有可拋出物件的父類)
│
┌───────────────┴───────────────┐
▼ ▼
Error Exception
(系統級錯誤,不可恢復) (程式級異常,可處理)
│ │
┌──────┴──────┐ ┌───────┴───────┐
▼ ▼ ▼ ▼
OutOfMemoryError StackOverflowError RuntimeException 其他 Exception 子類
(記憶體溢出) (棧溢出) (非受檢異常) (受檢異常)
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
NullPointerException ArrayIndexOutOfBoundsException IllegalArgumentException
(空指針) (下標越界) (非法參數)
其他受檢異常範例:
IOException、FileNotFoundException、SQLException、ClassNotFoundException...
兩大分類:處理義務不同
| 分類 | 代表 | 成因 | 編譯器要求 | 處理策略 |
| Error | OOM, StackOverflow | JVM/系統層面嚴重問題 | 不要求 | 通常不捕獲,讓程式崩潰、重啟、擴容 |
| RuntimeException 及子類 | NPE, 索引越界, 非法參數 | 程式邏輯缺陷 (Bug) | 不強制 (非受檢) | 修復代碼而非捕獲;全域攔截記錄日誌 |
| 其他 Exception 子類 | IOException, SQLException | 外部環境不確定性 | 強制 try-catch 或 throws (受檢) | 業務層捕獲處理:重試、降級、轉換、提示 |
經典異常速查表
| 異常類 | 典型觸發場景 | 排查方向 |
| NullPointerException | 呼叫 null 物件的方法/屬性 | 檢查物件初始化、傳參非空 |
| ArrayIndexOutOfBoundsException | 索引 < 0 或 >= length | 檢查迴圈邊界、集合大小 |
| IllegalArgumentException | 方法參數不符合預期 | 加強前置條件檢查 |
| ClassCastException | 強制轉型失敗 | 檢查泛型、instanceof 守護 |
| NumberFormatException | 字串解析數字失敗 | 輸入驗證、try-catch 包裝 |
| FileNotFoundException | 檔案路徑不存在 | 檢查路徑、權限、相對/絕對 |
| IOException | 讀寫失敗 (斷網、磁碟滿) | 重試、資源釋放、降級 |
異常物件核心屬性
try { risky(); }
catch (Exception e) {
e.getMessage(); // 簡短描述
e.getLocalizedMessage(); // 本地化描述
e.toString(); // 類名 + 訊息
e.printStackTrace(); // 完整堆疊追蹤輸出到 stderr
e.getStackTrace(); // 程式化存取堆疊幀陣列
e.getCause(); // 根因異常 (鏈式)
e.initCause(cause); // 手動設定根因
}
異常處理的代價與取捨
| 優勢 | 劣勢 |
| 錯誤資訊豐富 (類型/堆疊/原因鏈) | 建立異常物件、捕獲堆疊有效能開銷 |
| 強制處理受檢異常,減少遺漏 | 濫用導致代碼膨脹、try-catch 到處都是 |
| 支援跨層傳遞,關注點分離 | 異常流程難以靜態分析、追蹤 |
| 標準化機制,生態工具完善 | 受檢異常常被批評「染色」函數簽名 |
最佳實踐原則
- 異常用於異常:正常流程控制用 if/return/Optional,別用異常。
- 具體捕獲:catch 精確到業務異常類,避免 catch Exception/Throwable。
- 保留堆疊:包裝異常時傳入 cause:
throw new BizEx("msg", e)。
- 早拋出、晚捕獲:底層檢查拋出,業務層統一處理。
- 資源安全:try-with-resources 取代 finally 手動關閉。
- 日誌豐富:記錄完整堆疊、關鍵上下文參數、請求 ID。
總結
Java 異常機制以 Throwable 為根,Error/Exception 分支,受檢/非受檢區分處理義務。理解體系結構、掌握核心 API、遵循「早拋晚捕、具體處理、保留根因」原則,是寫出健壯、可診斷、可維護 Java 系統的關鍵。