SQLException) 往往不適合直接拋給上層業務邏輯解決方案:**建構子傳入 cause** 保留完整因果鏈。
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。
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,業務異常被吞沒!
}
}
// Java 7+ 自動處理:close() 異常會被抑制,附加在主要異常上
try (Resource res = acquire()) {
res.use();
} // 編譯器產生的 finally 會用 addSuppressed() 保留 close() 異常
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: 只能記錄日誌,無法保留
}
}
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();
}
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 形式附加**,完美解決異常丟失。
new BizException("msg", cause)log.error("msg", e) 而非 log.error(e.getMessage())cause,保留因果關係,除錯神器。finally 拋出異常會吞沒 try 異常,極度危險。addSuppressed() 完美共存主要與次要異常。finally 時腦中要響警鈴:**「這裡若拋異常,外面的異常就沒了!」**