首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 異常鏈與異常丟失:try-catch-finally 陷阱深度剖析

     編輯:Java 相關知識

異常鏈:將底層異常包裝保留完整堆疊

為什麼需要異常鏈?

  • 底層異常 (如 SQLException) 往往不適合直接拋給上層業務邏輯
  • 直接拋出會暴露實作細節、增加耦合
  • 但**丟棄原始異常**會導致除錯極度困難

解決方案:**建構子傳入 cause** 保留完整因果鏈。

支援異常鏈的建構子 (Throwable 及子類)


new Exception(String message, Throwable cause)
new Exception(Throwable cause)
new RuntimeException(String message, Throwable cause)
new Error(String message, Throwable cause)

其他檢查型異常 (如 IOException) 需用 initCause():


IOException ioe = new IOException("讀取失敗");
ioe.initCause(rootCause); // 事後設定 cause
throw ioe;

異常鏈實戰範例


public static void a() throws Exception {
    try { b(); }
    catch (Exception e) { throw new Exception("a() 失敗", e); } // 包裝
}
public static void b() throws Exception {
    try { c(); }
    catch (Exception e) { throw new Exception("b() 失敗", e); }
}
public static void c() throws Exception {
    throw new NullPointerException("c() 空指針");
}
public static void main(String[] args) {
    try { a(); }
    catch (Exception e) { e.printStackTrace(); }
}

輸出堆疊:


java.lang.Exception: a() 失敗
    at Demo.a(Demo.java:5)
    at Demo.main(Demo.java:20)
Caused by: java.lang.Exception: b() 失敗
    at Demo.b(Demo.java:10)
    at Demo.a(Demo.java:7)
Caused by: java.lang.NullPointerException: c() 空指針
    at Demo.c(Demo.java:15)
    at Demo.b(Demo.java:12)

完整保留從 main → a → b → c 的呼叫鏈與原始 NullPointerException。

異常丟失:finally 區塊的隱形殺手

經典丟失場景


public static void main(String[] args) {
    try {
        try {
            try { throw new Exception("重要異常 1"); }
            finally { throw new Exception("finally 異常 2"); } // 掩蓋前者
        }
        finally { throw new Exception("finally 異常 3"); }     // 再掩蓋
    }
    catch (Exception e) { e.printStackTrace(); }
}

輸出:


java.lang.Exception: finally 異常 3
    at Demo.main(Demo.java:15)

「重要異常 1」與「finally 異常 2」全部丟失!**

原因分析

JLS 規定:finally 區塊若拋出異常/返回,會取代 try 區塊的正常完成或異常拋出。外層 finally 同理再次取代。

真實世界風險


public void process() {
    Resource res = acquire();
    try {
        res.use(); // 可能拋出業務異常
    } finally {
        res.close(); // 若 close() 拋出 IOException,業務異常被吞沒!
    }
}

防禦性寫法:保護 finally 不丟失異常

1. finally 內部捕獲並記錄 (Java 7+ try-with-resources 最佳)


// Java 7+ 自動處理:close() 異常會被抑制,附加在主要異常上
try (Resource res = acquire()) {
    res.use();
} // 編譯器產生的 finally 會用 addSuppressed() 保留 close() 異常

2. 手動抑制異常 (Java 6 或無法用 try-with-resources)


Exception primary = null;
try {
    risky();
} catch (Exception e) {
    primary = e;
    throw e;
} finally {
    try {
        cleanup();
    } catch (Exception e) {
        if (primary != null) primary.addSuppressed(e); // Java 7+
        else throw e; // Java 6: 只能記錄日誌,無法保留
    }
}

3. 使用 Guava Closer / Apache Commons IO 等工具類


Closer closer = Closer.create();
try {
    InputStream in = closer.register(openInput());
    OutputStream out = closer.register(openOutput());
    copy(in, out);
} catch (Throwable t) {
    throw closer.rethrow(t); // 自動處理抑制異常
} finally {
    closer.close();
}

Java 7+ 抑制異常


try (AutoCloseable a = () -> { throw new Exception("A"); };
     AutoCloseable b = () -> { throw new Exception("B"); }) {
    throw new Exception("主要");
} catch (Exception e) {
    e.printStackTrace();
    for (Throwable s : e.getSuppressed()) {
        System.out.println("Suppressed: " + s);
    }
}

輸出:


java.lang.Exception: 主要
    at Demo.main(Demo.java:10)
Suppressed: java.lang.Exception: B
    at Demo.main(Demo.java:8)
Suppressed: java.lang.Exception: A
    at Demo.main(Demo.java:7)

**主要異常保留,關閉資源異常以 suppressed 形式附加**,完美解決異常丟失。

最佳實務總結

  1. 拋出異常時,永遠包裝 cause:new BizException("msg", cause)
  2. 優先使用 try-with-resources (Java 7+),自動處理資源關閉異常抑制
  3. 手寫 finally 時,內部務必 try-catch,避免 finally 異常吞沒主要異常
  4. 使用 addSuppressed() (Java 7+) 手動保留次要異常
  5. 自訂異常類別繼承 RuntimeException 可減少樣板碼,配合全域異常處理器統一轉換
  6. 日誌記錄完整堆疊:log.error("msg", e) 而非 log.error(e.getMessage())

總結

  • 異常鏈:建構子傳 cause,保留因果關係,除錯神器。
  • 異常丟失:finally 拋出異常會吞沒 try 異常,極度危險。
  • Java 7+ 解決方案:try-with-resources + addSuppressed() 完美共存主要與次要異常。
  • 寫 finally 時腦中要響警鈴:**「這裡若拋異常,外面的異常就沒了!」**
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved