C語言用返回碼/全局errno傳遞錯誤,易忽略、混雜業務邏輯。Java異常機制將錯誤處理從業務代碼中分離,提供類型安全、棧展開、 finally保證、多重捕獲、異常鏈的完整體系。本文從語法、類層次、JVM實現、最佳實踐四維度深度解析。
Throwable
├── Error // 嚴重問題,應用無法恢復(OOM、StackOverflow、LinkageError)
└── Exception // 應用可捕獲處理
├── RuntimeException // 非檢查異常:編程錯誤(NPE、IndexOOB、IllegalArg、ClassCast)
│ └── ... (Unchecked)
└── IOException等 // 檢查異常:外部環境故障(IO、SQL、網絡、解析)
└── ... (Checked)
try-catch或throws,API調用者必須面對
try {
riskyOperation();
} catch (IOException | SQLException e) { // Java 7+ 多捕獲
handle(e);
} catch (Exception e) {
handleGeneric(e);
} finally {
cleanup(); // 必執行(除非System.exit/死循環/ JVM崩潰)
}
// 自動關閉實現AutoCloseable的資源,異常抑制機制
try (FileInputStream in = new FileInputStream("a.txt");
FileOutputStream out = new FileOutputStream("b.txt")) {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) > 0) out.write(buf, 0, len);
} // 編譯器生成finally塊調用close(),異常鏈保留
void readFile(String path) throws IOException, ParseException { // 檢查異常必須聲明
if (!Files.exists(Path.of(path))) throw new FileNotFoundException(path);
return parse(Files.readString(Path.of(path)));
}
方法Code屬性包含異常表:
Exception table:
from to target type
0 10 20 Class java/io/IOException
10 20 30 Class java/lang/Exception
0 30 40 any // finally
from/to:受保護代碼區間(字節碼偏移)target:處理器入口偏移type:捕獲異常類(any表示finally)athrow指令創建異常對象,填充棧幀(fillInStackTrace)[from, to)、異常類型匹配(含子類)→ 跳轉targetThread.UncaughtExceptionHandler處理編譯器將finally代碼複製到:正常退出路徑、每個catch塊末尾、異常表any入口。確保必執行。JStry-finally同理。
try (Resource r = new Resource()) {
throw new Exception("主異常");
} catch (Exception e) {
// e.getSuppressed() 可獲取Resource.close()拋出的異常
}
Throwable內部維護suppressedExceptions數組,addSuppressed由編譯器生成代碼調用。
try {
parseConfig();
} catch (IOException e) {
throw new ConfigException("配置解析失敗", e); // 保留根因
}
getCause()返回根因initCause()事後設置| 反模式 | 危害 | 正確做法 |
|---|---|---|
捕獲後e.printStackTrace()吞沒 | 日誌丟失、監控盲區、調試困難 | 記錄完整堆棧log.error("msg", e)、轉換為業務異常拋出 |
catch (Exception e) {}大餿鍋 | 掩蓋Bug、NPE靜默、異常類型信息丟失 | 精準捕獲具體異常、多捕獲、保留異常鏈 |
| 用異常控制業務流程 | 性能差、可讀性差、棧展開開銷大 | 用返回值/Optional/Result模式表達預期結果 |
| 檢查異常濫用 | 調用鏈層層throws、模塊耦合、樣板代碼多 | 邊界轉換為非檢查異常、內部用非檢查、外部API文檔化 |
| finally中拋異常/返回值 | 掩蓋try/catch中異常、行為難預測 | finally僅清理資源,不改變控制流 |
| 自定義異常不提供構造器 | 無法攜帶信息、無法異常鏈 | 提供String、Throwable、String,Throwable三構造器 |
// Vavr / Result4j / 自定義
Result findUser(String id) {
return userRepo.find(id)
.map(Result::success)
.orElse(() -> Result.failure(Error.notFound(id)));
}
// 調用者
findUser("123").fold(
err -> handleError(err),
user -> process(user)
);
@RestControllerAdvice
class GlobalHandler {
@ExceptionHandler(BizException.class)
R handle(BizException e) { return R.fail(e.getCode(), e.getMessage()); }
@ExceptionHandler(Exception.class)
R handle(Exception e) { log.error("系統異常", e); return R.fail(500, "內部錯誤"); }
}
log.error("訂單創建失敗",
kv("orderId", orderId),
kv("userId", userId),
kv("errorCode", e.getCode()));
// 配合ELK/Splunk檢索分析
Java異常機制是類型系統、控制流、JVM棧管理、編譯器代碼生成協作的精密系統。掌握Checked/Unchecked分工、try-with-resources資源安全、異常表棧展開、finally語義、異常鏈根因、抑制異常,能寫出健壯、可觀測、可維護的錯誤處理代碼。記住:異常為異常情況而生,不用於正常流程控制。