Throwable
├── Error(嚴重錯誤,如 OutOfMemoryError,不應捕捉)
└── Exception
├── RuntimeException(非受檢,如 NPE、IndexOutOfBounds,邏輯錯誤)
└── 受檢例外(編譯強制處理,如 IOException、SQLException)
Java 強制規定:**除 Error 和 RuntimeException 外,所有例外必須被捕捉或聲明拋出**,這是強健代碼的基石。
public void readFile(String path) throws IOException {
Files.readAllLines(Paths.get(path)); // 未捕捉,拋給調用者
}
main() 若仍未處理,JVM 列印堆疊並終止程式。
try {
// 可能拋出例外的代碼(保持最小範圍)
String content = Files.readString(path);
process(content);
} catch (FileNotFoundException e) {
log.warn("檔案不存在: {}", path);
throw new BusinessException("資料來源缺失", e);
} catch (IOException e) {
log.error("讀取失敗", e);
throw new BusinessException("系統暫時不可用", e);
} finally {
// 無論是否發生例外、是否捕捉、是否 return,都會執行
// 釋放資源:關閉流、釋放鎖、清理 ThreadLocal
}
| 規則 | 說明 | 錯誤示範 | 正確做法 |
|---|---|---|---|
| catch 順序 | 子類在前,父類在後 | 先 catch Exception 再 catch IOException | 先 catch IOException 再 catch Exception |
| finally 必執行 | 即使 try/catch 有 return/throw | 在 finally 中 return 覆蓋原返回值 | 只做清理,不 return/throw |
| 資源關閉 | JDK 7+ try-with-resources 自動關閉 | 手動 close() 易漏、順序錯 | try (InputStream in = Files.newInputStream(path)) { ... } |
| 異常轉譯 | 保留原始堆疊 | throw new RuntimeException(e.getMessage()) | throw new BizException("業務含義", e) |
| 空 catch | 吞掉錯誤 = 製造定時炸彈 | catch (Exception e) {} | 至少記錄日誌或重拋 |
// 業務例外:繼承 RuntimeException,攜帶錯誤碼
public class BizException extends RuntimeException {
private final int code;
public BizException(int code, String message) { super(message); this.code = code; }
public BizException(int code, String message, Throwable cause) { super(message, cause); this.code = code; }
public int getCode() { return code; }
}
// 使用範例
if (balance < amount) {
throw new BizException(1001, "餘額不足,當前:" + balance + ",需求:" + amount);
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public R handleBiz(BizException e) {
log.warn("業務異常 [{}] {}", e.getCode(), e.getMessage());
return R.fail(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public R handleSys(Exception e) {
log.error("系統異常", e); // 完整堆疊進 ELK
return R.fail(500, "系統繁忙,請稍後再試");
}
}
Objects.requireNonNull、Preconditions.checkArgument 主動拋出。close()。log.error("msg", e),而非 log.error(e.getMessage())。Optional、Result、布林值。異常處理的核心不是「捕捉錯誤」,而是「建立可信任的錯誤傳遞管道」。正確的策略讓錯誤在正確層級被正確處理,錯誤資訊完整保留,資源確定釋放,業務語義清晰表達。這才是強健 Java 系統的基石。