Java 通過面向對象方式統一處理程序錯誤。異常機制將「正常業務流」與「錯誤處理流」分離,強制調用者面對可能失敗的操作。本文系統梳理異常繼承體系、受檢/非受檢分類、拋捕鏈傳播規則、自定義異常設計、核心 API 使用,並給出生產級最佳實踐清單。
java.lang.Throwable (所有異常共同祖先)
├── java.lang.Error (錯誤) — JVM 層面嚴重問題,程序不應捕獲
│ ├── VirtualMachineError (VM 錯誤)
│ │ ├── OutOfMemoryError (堆/元空間 OOM)
│ │ └── StackOverflowError (棧溢出)
│ ├── NoClassDefFoundError (類定義缺失)
│ └── LinkageError (鏈接錯誤)
└── java.lang.Exception (異常) — 程序可處理的業務/邏輯異常
├── java.lang.RuntimeException (運行時異常) — 非受檢,編譯器不強制處理
│ ├── NullPointerException (空指針)
│ ├── IllegalArgumentException (參數非法)
│ ├── IndexOutOfBoundsException (越界)
│ └── ArithmeticException (算術錯誤,如除零)
└── 其他 Exception 子類 — 受檢異常,必須 try-catch 或 throws
├── IOException (IO 錯誤)
├── SQLException (數據庫錯誤)
├── ClassNotFoundException (類未找到)
└── 自定義業務異常 (extends Exception)
| 維度 | Error | 受檢異常 (Checked Exception) | 非受檢異常 (Unchecked / RuntimeException) |
|---|---|---|---|
| 責任方 | JVM/環境 | 調用者 (API 契約) | 開發者 (邏輯缺陷) |
| 典型場景 | OOM、棧溢出、類加載失敗 | IO、網絡、數據庫、反射 | 空指針、越界、參數非法、除零 |
| 編譯器強制 | 否 | 是 (catch 或 throws) | 否 |
| 處理策略 | 日誌告警、重啟、擴容、修代碼 | 業務補償、重試、轉碼、向上拋 | 修復代碼邏輯、參數校驗、防禦性編程 |
| 是否應捕獲 | 極少 (如關閉資源) | 視業務而定 | 盡量不捕獲,預防為主 |
try {
int result = quotient(3, -1); // 可能拋 MyException / ArithmeticException
} catch (MyException e) { // 精確捕獲:先捕獲子類
logger.warn("業務規則違反: {}", e.getMessage());
} catch (ArithmeticException e) { // 再捕獲同級其他
logger.error("除數為零", e);
} catch (Exception e) { // 最後捕獲父類 (兜底)
logger.error("未知異常", e);
} finally {
// 無論是否異常,必執行 (除非 System.exit / JVM 崩潰)
resource.close();
}
try {
// 低層操作
} catch (SQLException e) {
// 包裝為業務異常,保留 cause
throw new BizException("用戶查詢失敗", e); // 關鍵:傳入 cause
}
// 異常鏈傳播:quotient() → main() → JVM
// printStackTrace() 會完整打印:BizException → SQLException → ...
子類在前,父類在後。若 `catch (Exception e)` 寫在 `catch (MyException e)` 前,後者永不可達,編譯器報錯「exception MyException has already been caught」。
public class BizException extends Exception {
private final int errorCode; // 業務錯誤碼
public BizException(String message, int errorCode) {
super(message); this.errorCode = errorCode;
}
public BizException(String message, Throwable cause, int errorCode) {
super(message, cause); this.errorCode = errorCode;
}
public int getErrorCode() { return errorCode; }
}
// 使用
throw new BizException("餘額不足", ErrorCode.INSUFFICIENT_BALANCE);
| 方法 | 返回 | 用途 |
|---|---|---|
| `getMessage()` | String | 僅異常描述信息 |
| `toString()` | String | 類名 + 描述 (如 `java.lang.NullPointerException: null`) |
| `printStackTrace()` | void | 打印完整堆棧到 System.err (含 cause 鏈) |
| `printStackTrace(PrintStream s)` | void | 輸出到指定流 (如日誌文件) |
| `getCause()` | Throwable | 獲取根因異常 |
| `getStackTrace()` | StackTraceElement[] | 程序化訪問堆棧幀 |
try (Connection c = ds.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
// 自動 close,即使異常也安全
}
try (InputStream in = new FileInputStream("a.txt");
OutputStream out = new FileOutputStream("b.txt")) {
// 複制邏輯
} catch (IOException e) {
// 若 in.close() 也拋異常,會被 suppressed 附加在 e 上
for (Throwable t : e.getSuppressed()) logger.warn("Suppressed: {}", t);
}
Java 異常機制以 `Throwable` 為根,分 `Error`(不可控) 与 `Exception`(可控),再分 `RuntimeException`(編程錯誤) 与 受檢異常(API 契約)。掌握拋捕傳播規則、異常鏈保留、自定義分層設計、try-with-resources 資源安全,配合「盡早拋出、遲捕獲、包裝不吞噬、日誌全信息」原則,即可構建健壯、可診斷、易維護的異常處理體系。