返回首頁

[Java] 例外處理

Java 例外比其他語言多一層「checked」概念——編譯器強迫你處理。這個設計常被批評(Scala / Kotlin 都拿掉了),但在 Java 裡仍是日常。這篇把整個階層、try-with-resources、自訂例外、跟最容易踩雷的 finally/吞例外問題串完。

兩種例外:checked vs unchecked

類型父類例編譯器強制處理
checkedException(非 RuntimeException)IOException、SQLException✅ 必須 try/catch 或 throws
uncheckedRuntimeExceptionNullPointerException、IllegalArgumentException❌ 不強制
error(也是 unchecked)ErrorOutOfMemoryError、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強制轉型失敗
NumberFormatExceptionInteger.parseInt("abc") 等
ArithmeticException1 / 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)。

參考文件

更新紀錄

  • 2026.11.05 內容更新
  • 2026.06.11 內容更新