程式運行環境複雜多變:檔案不存在、網路中斷、除數為零、記憶體耗盡……**異常機制**將「錯誤偵測」與「錯誤處理」分離,讓主流程碼保持乾淨,同時強制呼叫端面對可能的失敗情境,是構建**健壯、可維護**系統的基石。
java.lang.Throwable
├── java.lang.Error // 嚴重錯誤:JVM 層面,應用無法恢復(OutOfMemoryError, StackOverflowError)
└── java.lang.Exception // 應用層面可處理
├── java.lang.RuntimeException // 非受檢異常:邏輯錯誤,編譯器不強制處理
│ ├── NullPointerException
│ ├── ArithmeticException
│ ├── IndexOutOfBoundsException
│ └── IllegalArgumentException
└── 其他 Exception 子類別 // 受檢異常:外部環境不確定性,編譯器強制處理
├── IOException
├── SQLException
├── ClassNotFoundException
└── 自訂業務異常
| 維度 | 受檢異常 (Checked) | 非受檢異常 (Unchecked / Runtime) |
|---|---|---|
| 繼承自 | Exception (非 RuntimeException) | RuntimeException |
| 編譯器檢查 | ✅ 強制 try-catch 或 throws | ❌ 不強制 |
| 典型場景 | I/O、資料庫、網路、反射等外部資源 | 程式邏輯錯誤(空指標、越界、參數非法) |
| 處理策略 | 預期可恢復,需顯式處理 | 屬於 Bug,應修正程式碼而非捕獲 |
try {
// 高風險操作
FileInputStream fis = new FileInputStream("config.properties");
// ...
} catch (FileNotFoundException e) {
// 具體異常優先捕獲
log.error("配置檔缺失", e);
// 降級策略:使用預設配置
} catch (IOException e) {
// 父類異常後捕獲
log.error("讀取失敗", e);
} finally {
// 無論是否拋出異常,一定執行(除非 System.exit / JVM crash)
// 清理資源:關閉流、釋放鎖、歸還連線
}
關鍵規則:
catch區塊**由具體到一般**排序;finally保證執行(除非System.exit());return在try中遇到finally會**先執行 finally 再返回**。
// 將處理責任上傳給呼叫者
public void readConfig() throws IOException, SQLException {
// 內部不捕獲,直接拋出
new FileInputStream("x").close();
}
適用於:當前層級**無法妥善處理**,需由上層業務邏輯決定策略(重試、降級、回滾、向用戶提示)。
public void setAge(int age) {
if (age < 0 || age > 150)
throw new IllegalArgumentException("年齡必須 0~150,實際:" + age);
this.age = age;
}
用於**防禦性程式設計**:在進入核心邏輯前驗證前置條件,失敗快速、訊息明確。
// 業務異常基類
public class BusinessException extends RuntimeException {
private final int errorCode;
public BusinessException(int code, String msg) { super(msg); this.errorCode = code; }
public int getErrorCode() { return errorCode; }
}
// 具體業務異常
public class InsufficientBalanceException extends BusinessException {
public InsufficientBalanceException(String msg) { super(1001, msg); }
}
原則:業務異常通常繼承 RuntimeException(非受檢),避免汙染方法簽名;系統/資源異常繼承 Exception(受檢),強制處理。
| 場景 | finally 執行? | 返回值影響 |
|---|---|---|
| 正常完成 | ✅ | 無 |
| catch 後正常結束 | ✅ | 無 |
| catch 中 return | ✅ 先執行 finally 再返回 | finally 若修改返回值/再次 return,將覆蓋原返回 |
| try 中 return | ✅ 先執行 finally 再返回 | 同上 |
| System.exit(0) | ❌ JVM 直接終止 | 不適用 |
| JVM Crash / 死循環 | ❌ | 不適用 |
| 異常類別 | 類型 | 典型成因 | 處理建議 |
|---|---|---|---|
NullPointerException | Runtime | 呼叫 null 參考的方法/欄位 | 防禦性檢查、Optional、@NonNull 註解 |
ClassNotFoundException | Checked | 類路徑缺失、類名拼錯 | 檢查依賴、打包配置 |
ArithmeticException | Runtime | 整數除零 | 除前檢查分母 != 0 |
FileNotFoundException | Checked | 路徑錯誤、權限不足 | 檢查路徑、權限、檔案存在性 |
IOException | Checked | I/O 失敗(磁碟滿、網路斷) | 重試、降級、記錄日誌 |
SQLException | Checked | SQL 語法錯、鎖超時、連線斷 | 檢查 SQL、索引、連線池配置 |
catch (Exception e) 吞噬錯誤。throw e 或 throw new XxxException("msg", e),**絕不** throw new XxxException(e.getMessage()) 丟失堆疊。Objects.requireNonNull()、Optional、StringUtils.isNotBlank()。log.error("msg", e),非 log.error(e.getMessage())。Error(不可恢復)< Exception(可處理)→ RuntimeException(非受檢,邏輯錯)vs 其他(受檢,環境錯)。try-catch-finally(就地處理)、throws(上傳責任)、throw(主動防禦)。finally 幾乎總是執行,配合 try-with-resources 成標準資源管理模式。