Java 例外比其他語言多一層「checked」概念——編譯器強迫你處理。這個設計常被批評(Scala / Kotlin 都拿掉了),但在 Java 裡仍是日常。這篇把整個階層、try-with-resources、自訂例外、跟最容易踩雷的 finally/吞例外問題串完。
兩種例外:checked vs unchecked
| 類型 | 父類 | 例 | 編譯器強制處理 |
|---|---|---|---|
| checked | Exception(非 RuntimeException) | IOException、SQLException | ✅ 必須 try/catch 或 throws |
| unchecked | RuntimeException | NullPointerException、IllegalArgumentException | ❌ 不強制 |
| error(也是 unchecked) | Error | OutOfMemoryError、StackOverflowError | 不強制,一般業務邏輯不捕捉 |
Throwable 底下分成 Error 與 Exception;RuntimeException 是 Exception 的子類。RuntimeException、Error 及其子類都屬於 unchecked,其餘 throwable 類型是 checked。Error 通常表示一般應用程式不預期自行恢復的問題,並不是語法上不能捕捉。
try / catch / finally
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("parse failed: " + e.getMessage());
} finally {
System.out.println("always runs");
}
multi-catch(Java 7+)
同一段處理多種例外:
try {
Files.readString(Path.of("config.json")); // 可能丟 IOException
stmt.executeQuery("SELECT 1"); // 可能丟 SQLException
} catch (IOException | SQLException e) {
log.error("IO or DB failed", e);
}
multi-catch 內可用的成員由這些型別的共同上界決定,不能直接呼叫某一分支獨有的方法;也不能重新賦值給 e。同一組不能同時列出父類與子類,例如 IOException | FileNotFoundException。
try-with-resources(Java 7+)
實作 AutoCloseable 的資源會自動 close,就算中途丟例外也會關:
try (BufferedReader r = new BufferedReader(new FileReader("a.txt"))) {
return r.readLine();
}
// r 自動 close,不需 finally
多個資源用分號分隔,關閉順序與宣告相反:
try (Connection c = ds.getConnection();
PreparedStatement s = c.prepareStatement("...")) {
...
}
自己取得且負責關閉的 IO/DB 資源,優先用這個,不要自己寫 finally close。不是所有 Stream 都要關閉:由 Files.lines 等 IO 來源產生的串流要關,由集合產生的串流通常不需要。如果主體和 close() 同時丟例外,主體例外保留,關閉例外可從 getSuppressed() 取得。
本文 stmt、ds、log 等代表既有的資料庫/記錄器物件;... 是省略實作的示意,不能直接當成 Java 敘述編譯。
throws:往上拋
方法不想處理 checked exception 就在簽章宣告 throws:
public String readFirstLine(String path) throws IOException {
try (BufferedReader r = Files.newBufferedReader(Path.of(path))) {
return r.readLine();
}
}
呼叫者要嘛 try/catch、要嘛繼續往上 throws。
unchecked exception 不需要 throws 宣告(但寫了也沒錯,純文件用途)。
throw:主動丟
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("age cannot be negative: " + age);
}
this.age = age;
}
throw 是動詞(丟),throws 是宣告(宣告會丟),別搞混。
自訂例外
繼承 Exception(checked)或 RuntimeException(unchecked):
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException(String msg, Throwable cause) {
super(msg, cause);
}
}
自家 library 通常定一個 base:
public class ApiException extends RuntimeException { ... }
public class NotFoundException extends ApiException { ... }
public class RateLimitException extends ApiException { ... }
使用者就能 catch (ApiException e) 一網打盡。
例外鏈 chained exception
包裝低階例外、保留原 cause:
try {
Files.readString(Path.of("config.json"));
} catch (IOException e) {
throw new ConfigException("cannot load config", e); // 第二參數是 cause
}
printStackTrace 會印出 Caused by: ...,debug 不會斷線。
try / catch / finally 的微妙之處
finally 在正常控制流程離開時會跑
try {
return 1;
} finally {
System.out.println("still runs"); // 會跑,return 之後、回呼叫端之前
}
這不等於任何情況都保證執行:例如 JVM 被終止、呼叫 System.exit,或執行緒一直停在 try 內,都不能依賴 finally 完成清理。
finally 內 return 會吃掉 try 的 return(陷阱)
int f() {
try {
return 1;
} finally {
return 2; // ❌ 最後實際回 2,吞掉 1,也會吞例外
}
}
finally 不要寫 return 或 throw,純做釋放。
別這樣寫
// ❌ 吞例外
try {
work();
} catch (Exception e) {
// do nothing
}
// ❌ 太籠統
try {
work();
} catch (Exception e) {
log.error("failed"); // 沒帶 e,stack trace 沒了
}
正確:
try {
work();
} catch (SpecificException e) {
log.error("work failed", e); // 第二參數帶 e,stack 才會印出
throw e; // 不知道怎麼處理就讓它往上
}
常見內建例外
| 例外 | 觸發 |
|---|---|
NullPointerException | 對 null 呼叫方法或存欄位 |
IllegalArgumentException | 參數不合法 |
IllegalStateException | 物件狀態不允許這個操作 |
IndexOutOfBoundsException | 陣列 / list 越界 |
ClassCastException | 強制轉型失敗 |
NumberFormatException | Integer.parseInt("abc") 等 |
ArithmeticException | 1 / 0(int 除法) |
IOException | 檔案 / 網路 IO 失敗(checked) |
InterruptedException | 執行緒被中斷(checked) |
NullPointerException(NPE)很常見。Java 8 的 Optional 適合用在「可能沒有結果」的方法回傳值,但不是所有 null 的通用替代品。Helpful NPE messages 在 Java 14 加入,Java 15 起預設啟用;可用 -XX:+ShowCodeDetailsInExceptionMessages 控制。更根本的是明訂哪些值可以缺少,並在邊界驗證。
下一篇進集合框架——List / Map / Set / Queue,與何時選哪一個實作(ArrayList vs LinkedList、HashMap vs TreeMap)。
參考文件
- JLS 21:Exceptions
- Oracle:try-with-resources 與 suppressed exceptions
- JEP 358:Helpful NullPointerExceptions/預設啟用的 Java 15 變更
更新紀錄
- 2026.11.05 內容更新
- 2026.06.11 內容更新

