public class ExceptionTrapDemo {
public static void main(String[] args) {
try {
int a = 1 / 0; // ArithmeticException
} catch (Exception e) {
System.out.println("catch 塊");
String s = null;
System.out.println(s.length()); // NullPointerException
} finally {
System.out.println("finally 塊");
int[] arr = {1, 2, 3};
System.out.println(arr[6]); // ArrayIndexOutOfBoundsException
}
}
}
try 拋出 ArithmeticException。catch,打印 "catch 塊",隨後拋出 NullPointerException。catch 中的異常暫存,執行 finally。finally 打印 "finally 塊",拋出 ArrayIndexOutOfBoundsException。ArrayIndexOutOfBoundsException 向外傳播,**NullPointerException 被吞噬(丟失)**。若移除 finally 塊,則拋出 NullPointerException。
| 場景 | 最終拋出異常 | 被吞噬異常 |
|---|---|---|
| try 拋 A,catch 拋 B,finally 正常 | B | A |
| try 拋 A,catch 正常,finally 拋 C | C | A |
| try 正常,catch 拋 B,finally 拋 C | C | B |
| try 拋 A,catch 拋 B,finally 拋 C | C | A, B |
| try return,finally return | finally 的 return 值 | try 的 return 值 |
鐵律:finally 中的 拋出異常 或 return/break/continue 會**完全覆蓋** try/catch 的異常或返回值。
try {
riskyOperation();
} catch (Exception e) {
log.error("業務異常", e);
throw new BusinessException(e);
} finally {
try {
resource.close(); // 可能拋 IOException
} catch (IOException e) {
log.warn("資源關閉失敗", e); // 僅記錄,不拋出
// 或添加到 Suppressed:primary.addSuppressed(e) (Java 7+)
}
}
// 自動調用 close(),異常自動添加為 Suppressed
try (Resource res = new Resource()) {
res.riskyOperation();
} catch (Exception e) {
// e.getSuppressed() 可獲取 close() 拋出的異常
throw new BusinessException(e);
}
try {
// ...
} catch (SQLException e) {
throw new DataAccessException("數據庫訪問失敗", e); // 構造函數傳入 cause
}
public int trap() {
try {
return 1;
} finally {
return 2; // 編譯器警告:finally 塊不正常完成
}
}
// 調用結果:2,try 的 return 1 被丟棄
最佳實踐:finally **絕不**包含 return/throw/break/continue,僅用於資源清理、狀態重置、日誌記錄。
try (AutoCloseable res = () -> { throw new IOException("close 失敗"); }) {
throw new SQLException("業務失敗");
} catch (Exception e) {
System.out.println("主異常: " + e); // SQLException
for (Throwable t : e.getSuppressed()) {
System.out.println("被壓制: " + t); // IOException
}
}
try-with-resources 自動將資源關閉異常作為 Suppressed 附加到主異常,避免異常丟失,便於診斷。
finally 中拋出異常/返回會吞噬 try/catch 的異常/返回值。finally 時,**必須內部捕獲異常**,或使用 try-with-resources。throw new XxxException("msg", cause)。try-with-resources 替代手動 finally 關閉資源。log.error("msg", e),而非 log.error(e.getMessage())。