| 特性 | Exception (異常) | Error (錯誤) |
|---|---|---|
| 繼承自 | java.lang.Exception | java.lang.Error |
| 性質 | 程式邏輯可預期、可處理的異常狀況 | JVM/系統級嚴重問題,通常無法恢復 |
| 典型例子 | NullPointerException, IOException, SQLException | OutOfMemoryError, StackOverflowError, NoClassDefFoundError |
| 處理責任 | 開發者**必須**捕獲或拋出 | 非應用層責任,通常只能記錄並終止 |
| 檢查機制 | 受檢異常編譯強制處理 | 非受檢,編譯器不強制 |
兩者共同繼承自 Throwable,只有 Throwable 及其子類可被 throw/catch。
Throwable
├── Error (系統錯誤,不處理)
└── Exception
├── RuntimeException (非受檢異常 Unchecked)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── ClassCastException
└── 其他 (受檢異常 Checked)
├── IOException
├── SQLException
├── ClassNotFoundException
└── InterruptedException
Exception 但非 RuntimeException。編譯器強制 try-catch 或 throws,適用「呼叫者**合理預期能恢復**」的場景 (如檔案不存在、網路中斷)。RuntimeException。通常為**程式邏輯錯誤** (空指標、越界、參數非法),應透過**防禦性程式設計**預防,而非捕獲處理。try {
// 高風險代碼
FileInputStream fis = new FileInputStream("data.txt");
} catch (FileNotFoundException e) {
// 處理特定異常
logger.error("檔案不存在", e);
} catch (IOException e) {
// 處理父類異常 (須放在子類之後)
logger.error("IO 錯誤", e);
} finally {
// 無論是否異常都會執行 (除非 System.exit)
// 釋放資源、關閉連線
}
// 實作 AutoCloseable 的資源會自動關閉,順序相反
try (FileInputStream fis = new FileInputStream("data.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = br.readLine()) != null) {
process(line);
}
} catch (IOException e) {
logger.error("讀取失敗", e);
}
// 無需 finally 手動 close,編譯器自動生成隱含 finally
catch (FileNotFoundException | SocketException e) {
// Java 7+ 同一塊處理多種無繼承關係的異常
logger.error("資源不可用", e);
}
拋出新異常時,**務必保留原始原因**,否則除極困難:
try {
jdbcTemplate.update(sql);
} catch (DataAccessException e) {
// 錯誤:原始堆疊丟失
// throw new BusinessException("更新失敗");
// 正確:建立異常鏈
throw new BusinessException("更新失敗", e); // 建構子傳入 cause
// 或手動設定
BusinessException be = new BusinessException("更新失敗");
be.initCause(e);
throw be;
}
列印堆疊時會顯示 Caused by: ... 完整鏈路。
RuntimeException (非受檢),配合全域異常處理器統一轉碼碼/訊息code、data 欄位,支援國際化訊息Exception 結尾,如 InsufficientBalanceException| 反模式 | 問題 | 正確做法 |
|---|---|---|
catch (Exception e) {} 吞沒異常 | 錯誤無聲無息,難以排查 | 至少 log.error,或拋出自訂異常 |
throw new Exception(e.getMessage()) | 丟失堆疊追蹤 | throw new MyException("msg", e) |
| 用異常控制流程 | 效能差、語意不清 | 用條件判斷、Optional、Result 物件 |
| finally 裡拋出異常 | 掩蓋 try 區塊的原始異常 | finally 只做清理,不拋出異常 |
捕獲後直接 e.printStackTrace() | 輸出到 stdout,無法整合日誌系統 | 用 SLF4J/Log4j2 log.error("msg", e) |
// ✅ 推薦:try-with-resources
public void readConfig() {
try (InputStream is = Files.newInputStream(Paths.get("config.yaml"))) {
parse(is);
} catch (IOException e) {
throw new ConfigException("讀取配置失敗", e);
}
}
// ❌ 避免:手動 close 易漏
public void readConfigOld() {
InputStream is = null;
try {
is = Files.newInputStream(Paths.get("config.yaml"));
parse(is);
} catch (IOException e) {
throw new ConfigException("讀取配置失敗", e);
} finally {
if (is != null) {
try { is.close(); } catch (IOException ignored) {}
}
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public R<Void> handleBiz(BusinessException e) {
return R.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public R<Void> handleOther(Exception e) {
log.error("系統異常", e);
return R.fail(500, "系統內部錯誤");
}
}
cause 或 initCause()掌握這些原則,能寫出**健壯、可維護、易除錯**的 Java 系統。