異常處理不是「寫幾個 catch 塊」,而是設計錯誤傳播路徑、保證資源釋放、提供可操作的錯誤信息。本文從語法基礎、執行流程、多重捕獲規則、異常轉換鏈、資源管理、生產級最佳實踐六個維度,構建完整異常處理知識體系。
try {
// 受監控代碼區
riskyOperation();
} catch (IOException e) { // 捕獲特定異常
handleIOError(e);
} catch (SQLException | TimeoutException e) { // JDK 7+ 多異常合併
handleDbOrTimeout(e);
} catch (Exception e) { // 兜底捕獲(父類在後)
log.error("未預期錯誤", e);
throw new RuntimeException(e); // 可轉型拋出
} finally {
// 清理資源:關流、釋放鎖、關連接
closeResources();
}
try {
A(); B(); // B 拋異常
return 1; // 不執行
} catch (Exception e) {
C(); // 執行捕獲塊
return 2; // 設置返回值 2
} finally {
D(); // **必執行**(極少數除外)
// return 3; // ⚠️ 覆蓋外層返回值,極度不推薦
}
// 實際返回:2
// 若 finally 拋異常:外層異常被覆蓋,finally 異常傳播
子類先捕獲,父類後捕獲,否則編譯錯誤 "unreachable catch block"。
// 錯誤示範
catch (Exception e) { }
catch (IOException e) { } // 編譯錯誤:IOException 是 Exception 子類,永不可達
// 正確
catch (FileNotFoundException e) { } // 最具體
catch (IOException e) { } // 父類
catch (Exception e) { } // 更上層
// JDK 7+ 合併捕獲(同層級、無繼承關係)
catch (IOException | SQLException | TimeoutException e) {
// e 類型為公共父類(Exception),不可重新拋出具體子類
}
try {
jdbcTemplate.update(sql);
} catch (DataAccessException e) {
// ❌ 錯誤:丟失原始堆棧
throw new BusinessException("數據庫錯誤");
// ✅ 正確:構造器傳入 cause
throw new BusinessException("用戶保存失敗", e);
// ✅ 正確:initCause(異常無帶 cause 構造器時)
BusinessException be = new BusinessException("用戶保存失敗");
be.initCause(e);
throw be;
}
日誌記錄鐵律:log.error("業務含義描述", e) —— 參數順序:信息在前,異常對象在後,框架自動打印完整堆棧。
// 手工 finally 痛點:代碼冗長、關閉順序易錯、異常抑制機制缺失
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) { process(rs); }
} // 自動按**逆序**關閉:rs → ps → conn
// 若關閉過程拋異常,被**抑制**附加到主異常(getSuppressed() 可獲取)
class TempFileManager implements AutoCloseable {
private final File tempDir;
public TempFileManager() { tempDir = Files.createTempDirectory("app").toFile(); }
public File newTempFile() throws IOException { return File.createTempFile("tmp", ".dat", tempDir); }
@Override public void close() { // 刪除整個臨時目錄
FileUtils.deleteQuietly(tempDir);
}
}
// 使用
try (TempFileManager tm = new TempFileManager()) {
File f1 = tm.newTempFile();
File f2 = tm.newTempFile();
// 退出 try 自動清理
}
| 維度 | 受檢異常 (Checked) | 非受檢異常 (Unchecked) |
|---|---|---|
| 繼承 | Exception (非 RuntimeException) | RuntimeException 子類 |
| 編譯器強制 | 必須 catch 或 throws | 無強制 |
| 語義 | **可預期、可恢復**的業務錯誤 | **編程錯誤、不可恢復**的系統故障 |
| 典型例子 | IOException、SQLException、TimeoutException、自定義 BizException | NullPointerException、IllegalArgumentException、IndexOutOfBounds、自定義 SystemException |
| 架構層面 | Service 層拋出,Controller 捕獲轉友好提示 | 框架全局攔截器捕獲,記錄日誌、返回 500/錯誤碼 |
SQLException → Service 包裝為 BizException → Controller 統一處理返回錯誤碼/頁面。catch (Exception e) { } 嚴禁;最小動作 log.error("", e)。throw new BizException("訂單號 {} 不存在", orderId),而非 "ERROR"。cause,initCause 兜底。@ControllerAdvice、Servlet error-page、Thread setDefaultUncaughtExceptionHandler。try { map.get(key) } catch { ... } 判斷 key 存在 —— 用 containsKey。@throws BizException 當訂單不存在時。異常處理是代碼健壯性的護城河。內化本文流程圖、轉換鏈、資源管理模式與最佳實踐,能讓你的系統在錯誤面前依然優雅、可追蹤、可恢復。