異常處理是 Java 程式健壯性的基石。本文從編譯器強制檢查機制切入,系統闡述異常體系、處理語法、分類邏輯、設計原則與常見反模式,並附實戰案例。
簡單的 `readLine()` 呼叫隱含多重假設:鍵盤正常、OS 傳輸正常、JVM 接收正常。任一環節失敗即拋出 `IOException`。編譯器強制要求「捕獲」或「聲明」,迫使開發者正視失敗場景。
Throwable
├── Error (嚴重系統錯誤,如 OutOfMemoryError,應用無法恢復)
└── Exception (應用層可預期/可恢復問題)
├── RuntimeException (非檢查異常:NullPointerException, IndexOutOfBoundsException, ArithmeticException...)
└── Checked Exceptions (檢查異常:IOException, SQLException, ClassNotFoundException...)
try {
String line = reader.readLine(); // 可能拋出 IOException
System.out.println("Read: " + line);
} catch (IOException e) {
System.err.println("Exception encountered: " + e);
} finally {
System.out.println("End."); // 無論成敗必執行(除 JVM 退出/斷電/finally 內拋出未處理異常)
}
規則:`try` 後至少一個 `catch` 或 `finally`;順序固定 `try → catch → finally`;多 `catch` 依宣告順序匹配首個符合類型;可巢狀;可在 `catch` 中重新拋出。
public void errorProneMethod() throws IOException, SQLException {
// 可能拋出的檢查異常必須聲明
}
調用者必須捕獲或繼續聲明。覆寫方法聲明的異常必須是父類聲明異常的同類或子類。非檢查異常可聲明可不聲明。
| 分類 | 代表 | 編譯器檢查 | 處理策略 |
|---|---|---|---|
| 檢查異常 | IOException, SQLException | 強制 | 業務邏輯處理、資源重試、轉換為業務異常 |
| 非檢查異常 | NullPointerException, IndexOutOfBoundsException | 不檢查 | 防禦性編程預防(非空判斷、邊界檢查),不宜捕獲 |
| Error | OutOfMemoryError, StackOverflowError | 不檢查 | 記錄日誌、優雅關閉,不可恢復 |
| 自定義異常 | InsufficientFundsException | 視父類而定 | 承載業務語境資訊(餘額、請求金額等) |
| 反模式 | 代碼特徵 | 危害 |
|---|---|---|
| 吞噬異常 | `catch (Exception e) {}` 或只印堆疊不處理 | 掩蓋錯誤,導致後續邏輯在錯誤狀態下運行,極難除錯 |
| 籠統捕獲 | `catch (Exception e) { handle(); }` | 混淆異類錯誤,單一恢復邏輯不適用所有場景,新增異常類型被誤處理 |
| 具體轉通用 | `catch (IOException e) { throw new Exception(e.getMessage()); }` | 丟失異常類型資訊,上層無法針對性處理 |
| 濫用異常控制流程 | 用 `try-catch` 替代 `if (obj != null)` 等防禦性判斷 | 效能低、語義不清、掩蓋真正程式錯誤 |
// 自定義檢查異常,攜帶業務語境
class InsufficientFundsException extends Exception {
private Bank bank; private double amount;
InsufficientFundsException(Bank b, double a) { bank=b; amount=a; }
public String getMessage() {
return "Balance: " + bank.balance + ", Requested: " + amount;
}
}
// 業務邏輯聲明拋出
class Bank {
double balance;
void withdraw(double amt) throws InsufficientFundsException {
if (balance < amt) throw new InsufficientFundsException(this, amt);
balance -= amt;
}
}
// 前端捕獲並呈現
try {
Bank b = new Bank(50);
b.withdraw(100);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage()); // Balance: 50.0, Requested: 100.0
}
異常處理非樣板代碼,而是領域建模的一部分。遵循「具體捕獲、語境轉換、保留鏈路、拒絕吞噬」,配合自定義業務異常承載語義資訊,才能構建可維護、可診斷、健壯的 Java 系統。