異常處理是 Java 基礎課必講,但真正掌握「如何優雅地處理異常」的開發者寥寥無幾。本文劃分三個層次——好(優雅)、壞(粗暴)、醜(反模式),結合檢查型/非檢查型異常機制,給出落地指導。
| 分類 | 代表 | 處理要求 |
|---|---|---|
| 可檢查異常 | IOException, SQLException | 強制 try-catch 或 throws 聲明 |
| 不可檢查異常 | NullPointerException, IllegalArgumentException | 編譯不強制,靠代碼邏輯預防 |
| Error | OutOfMemoryError, StackOverflowError | 系統級,應用無法恢復,不應捕獲 |
💡 記憶口訣:「有 throw 必有 throws」(僅針對可檢查異常)。
try (FileInputStream fis = new FileInputStream(path)) {
// 業務邏輯
} catch (FileNotFoundException e) {
log.error("配置文件缺失: {}", path, e); // 記錄完整堆棧
throw new ConfigLoadException("啟動配置缺失", e); // 包裝為業務異常拋出
} catch (IOException e) {
log.error("讀取配置失敗", e);
throw new ConfigLoadException("配置讀取異常", e);
}
Exception。throw new BizEx("msg", cause) 保留根因。try {
riskyCall();
} catch (Exception e) {
e.printStackTrace(); // 僅打印控制台,生產環境不可見
return null; // 調用方無法區分「業務為空」vs「系統出錯」
}
printStackTrace() 輸出到標準錯誤流,容器化部署易丟失。null 或錯誤碼,破壞類型安全,調用方被迫防禦性編程。| 反模式 | 代碼特徵 | 危害 |
|---|---|---|
捕獲 Throwable/Error | catch (Throwable t) | 吞噬 OOM、StackOverflow,導致 JVM 無法崩潰重啟 |
| 空 catch 塊 | catch (Exception e) {} | 故障無感知,數據損壞擴散 |
| 異常控制流程 | 用 throw 替代 if/return | 性能極差,可讀性極低 |
| 過度包裝 | 層層 throw new Ex(new Ex(new Ex(...))) | 堆棧膨脹,定位困難 |
| 檢查異常泛濫 | 方法簽名拋出 5+ 個 checked exception | 調用方疲勞,被迫 throws Exception |
BizException(code, msg)、SysException,統一錯誤碼規範。@RestControllerAdvice 統一轉換為標準 API 響應(RFC 7807 Problem Details)。traceId、spanId,便於 ELK 檢索。@Retryable + @CircuitBreaker (Resilience4j) 處理瞬態故障。throw 分支,Mutation Testing 驗證異常處理健壯性。異常處理的本質是「失敗時的契約」。好代碼讓異常可見、可追蹤、可恢復、可度量;壞代碼掩蓋真相;醜代碼製造災難。選擇「好」,是專業工程師的底線。