C 語言依賴返回碼(如 fopen 返回 NULL)表示錯誤,易被忽略導致程式脆弱。Java 強制將錯誤處理職責前移到方法設計者:方法聲明拋出的受檢異常,調用者必須捕獲或繼續拋出,否則編譯不通。這是編譯器層面的「契約強制」。
Throwable
├── Error // 嚴重系統錯誤(OOM、StackOverflow),不可恢復,不捕獲
└── Exception // 應用層異常
├── RuntimeException // 運行時異常(空指針、越界、除零),非受檢,無需聲明捕獲
└── 受檢異常 // IOException、SQLException 等,必須 try-catch 或 throws
自定義異常繼承 Exception(受檢)或 RuntimeException(非受檢),建議:業務邏輯可預期、可恢復用受檢;編程錯誤、不可恢復用非受檢。
// 方法聲明拋出
int isLegal(int dt) throws LowZeroException {
if (dt >= 0) return dt;
throw new LowZeroException(); // 拋出異常對象
}
// 調用者捕獲
int myMethod(int dt) {
try {
return isLegal(dt); // 正常返回
} catch (LowZeroException e) { // 捕獲並處理
System.out.println("數據非法!");
return -1;
}
}
關鍵點:
throw 立即中斷當前方法,異常對象沿調用棧向上冒泡。catch 參數類型匹配(含子類)則進入處理塊,異常對象生命在這裡終結(除非重新拋出)。finally 不管有無異常都執行,用於資源釋放。
int myMethod2(int dt) throws LowZeroException { // 繼續拋出
return myMethod(dt); // 未捕獲,異常傳遞給調用者
}
// 頂層捕獲
try {
myMethod2(-1);
} catch (LowZeroException e) {
e.printStackTrace();
}
輸出棧追蹤:
LowZeroException
at Example.isLegal(Example.java:10)
at Example.myMethod(Example.java:18)
at Example.myMethod2(Example.java:25)
at Example.main(Example.java:30)
追蹤從拋出點開始,到當前線程起點(main)結束。每一行對應一個棧幀,精確定位源碼行號。
try { ... }
catch (IOException | SQLException e) { // 同塊處理多種異常
log.error(e);
}
try { ... }
catch (SQLException e) {
throw new ServiceException("數據庫操作失敗", e); // 包裝原始異常
}
日誌輸出時 e.getCause() 可見根因,避免信息丟失。
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 使用資源
} // 編譯器自動生成 finally 關閉,按逆序關閉,異常抑制機制保證首異常不丟失
NullPointerException、IllegalArgumentException)。try { risky(); }
finally { cleanup(); } // 若 cleanup() 拋出異常,原異常丟失!
// 改用 try-with-resources 或手動抑制
Java 異常機制本質是強類型的錯誤處理協議。掌握棧追蹤閱讀、異常鏈保留、資源自動管理、受檢/非受檢合理選型,能寫出健壯、可診斷、可維護的代碼。記住:異常是給程序員看的,不是給用戶看的——用戶看到的應是友好錯誤頁面,日誌系統才是異常的真正歸宿。