異常體系架構圖
java.lang.Throwable
├── java.lang.Error // 嚴重錯誤,程序無法恢復 (OOM, StackOverflow)
└── java.lang.Exception // 可處理的異常
├── java.lang.RuntimeException // 非受檢異常 (Unchecked)
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ ├── IndexOutOfBoundsException
│ └── ArithmeticException
└── 受檢異常 (Checked Exception) // 編譯期強制處理
├── IOException
├── SQLException
├── ClassNotFoundException
└── 自定義業務異常
核心概念區分
| 維度 |
受檢異常 (Checked) |
非受檢異常 (Unchecked/Runtime) |
| 繼承關係 |
Exception 直接子類 |
RuntimeException 子類 |
| 編譯檢查 |
強制 try-catch 或 throws |
編譯期不檢查 |
| 典型場景 |
外部資源不可控 (IO、DB、網絡) |
邏輯錯誤、編程缺陷、參數非法 |
| 處理策略 |
預期可恢復,需顯式處理 |
預期不可恢復,修復代碼而非捕獲 |
三種異常處理方式
1. 捕獲處理 (try-catch-finally)
try {
// 高風險代碼
FileInputStream fis = new FileInputStream("data.txt");
} catch (FileNotFoundException e) {
// 具體異常優先捕獲
log.error("文件不存在", e);
throw new BusinessException("配置文件缺失", e);
} catch (IOException e) {
// 父類異常後捕獲
log.error("IO錯誤", e);
} finally {
// 必執行清理:關流、釋放鎖、關連接
// 即使 catch 中 return/throw 也會執行
// 唯一不執行:System.exit(0) 終止 JVM
}
2. 拋出聲明 (throws)
public void readConfig() throws IOException, SQLException {
// 不處理,交給調用者決定
// 調用者必須 catch 或繼續 throws
}
3. 主動拋出 (throw)
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年齡非法: " + age);
}
this.age = age;
}
自定義異常最佳實踐
// 業務異常基類
public class BusinessException extends RuntimeException {
private final int code;
private final String userMessage; // 面向用戶的友好提示
public BusinessException(int code, String userMessage, String devMessage, Throwable cause) {
super(devMessage, cause);
this.code = code;
this.userMessage = userMessage;
}
// 靜態工廠方法
public static BusinessException of(int code, String userMsg, String devMsg) {
return new BusinessException(code, userMsg, devMsg, null);
}
}
// 具體業務異常
public class InsufficientBalanceException extends BusinessException {
public InsufficientBalanceException(String accountId, BigDecimal balance, BigDecimal required) {
super(400, "餘額不足", String.format("賬戶 %s 餘額 %.2f 不足支付 %.2f", accountId, balance, required), null);
}
}
常見異常類型速查
| 異常 |
原因 |
類型 |
| NullPointerException |
對 null 引用調用方法/訪問字段 |
Runtime |
| ClassNotFoundException |
類加載器找不到指定類 |
Checked |
| ArithmeticException |
數學運算錯誤 (除零、溢出) |
Runtime |
| FileNotFoundException |
文件路徑不存在/不可讀 |
Checked |
| IOException |
IO 操作失敗 (讀寫、關閉、網絡) |
Checked |
| SQLException |
數據庫操作失敗 (SQL語法、連接、事務) |
Checked |
黃金法則與避坑指南
- 異常不用於控制流程:不要用 try-catch 替代 if-else 判斷
- 具體異常優先捕獲:子類在前,父類在後
- 不吞沒異常:
catch (Exception e) {} 是大忌,至少記錄日誌
- 保留堆棧信息:拋出新異常時帶上
cause:throw new BizEx("msg", e)
- finally 中不返回值:會覆蓋 try/catch 的返回/異常
- 資源用 try-with-resources:JDK 7+ 自動關閉實現
AutoCloseable 的資源
- 業務異常用 RuntimeException:減少模板代碼,由全局異常處理器統一轉換為錯誤響應
- 日誌記錄原則:
log.error("業務描述", e) 而非 log.error(e.getMessage())
異常處理的本質是將錯誤信息從發生點傳遞到能夠處理的地方。合理設計異常體系,能讓系統在故障時快速定位、優雅降級、便於運維排查。