CodeSprout程式萌芽

CHAPTER 04 · JAVA 11 FOUNDATIONS

Java 例外處理機制

辨認程式錯誤,讀懂例外傳遞,用 try/catch/finally、throw 與 throws 清楚處理失敗。

這一章學會
  • 區分編譯錯誤、執行例外與邏輯錯誤
  • 追蹤 try/catch/finally 執行順序
  • 理解 checked exception、throw 與 throws
先預測結果,再看輸出。

每個 Java 範例都是獨立的 Main.java,可用 JDK 11 編譯執行;請一次複製一個範例。題庫使用 public class Solution 中的 public static solve(...) 方法,請保留題目指定的型別與參數,使用 return 交回答案,不必另外讀取輸入或撰寫 main。

01|先辨認是哪一種錯誤

教材將 bug 分成編譯時期、執行時期與商業邏輯三類。少分號、型別不合等錯誤由編譯器指出;陣列越界或不合法轉型可能在執行時拋出例外;公式或邊界判斷寫錯,則可能正常執行卻得到錯答案。

  • 編譯錯誤:先找第一個錯誤位置,修正語法、名稱、型別或 import。
  • 執行例外:讀例外類別、訊息與 stack trace,找出最接近錯誤來源的自寫程式行。
  • 邏輯錯誤:用已知答案、小資料與邊界案例核對;try/catch 不會修正錯誤的計算公式。

教材對照:PDF 第 2 頁

02|Throwable、Error 與 Exception

Throwable 是可被拋出物件的根型別,主要分成 Error 與 Exception。RuntimeException 及其子類別、Error 及其子類別屬於 unchecked;其餘例外類別屬於 checked。例如 IOException 是 checked,ArithmeticException 與 NullPointerException 是 unchecked。

  • checked exception:呼叫可能拋出的操作時,必須捕捉,或在方法上用 throws 宣告向外傳遞。
  • unchecked exception:編譯器不強制 catch/throws;仍應檢查不合法狀態與修正程式。
  • Error 通常表示 JVM 或資源等嚴重問題;不要用 catch(Throwable) 把所有錯誤都當成一般輸入問題吞掉。

教材對照:PDF 第 3–5、12–13 頁

03|try 與 catch 的執行路線

try 放可能失敗的操作。例外發生時,try 中後續敘述會被跳過,改找型別匹配的 catch。catch 處理完成後,再接著執行整組 try/catch 後面的程式。例外物件的 getMessage 可取說明,printStackTrace 可顯示呼叫路徑。

JAVA 11 · Main.java程式碼
public class Main {
    public static void main(String[] args) {
        int divisor = 0;
        try {
            System.out.println(20 / divisor);
            System.out.println("這行不會執行");
        } catch (ArithmeticException error) {
            System.out.println("無法進行整數除法");
        }
        System.out.println("繼續執行");
    }
}
預期輸出
無法進行整數除法
繼續執行

教材對照:PDF 第 6–10 頁

04|多個 catch 由具體到一般

若多個 catch 有繼承關係,子類別要放前面,否則會先被父類別接走,讓後面的 catch 無法到達。每次例外只會進入第一個匹配的 catch。沒有繼承關係的例外也可以在相同處理方式時使用 multi-catch,以 | 分隔。

JAVA 11 · Main.java程式碼
public class Main {
    public static void main(String[] args) {
        String[] texts = {"12", "bad", null};
        for (String text : texts) {
            try {
                System.out.println(Integer.parseInt(text.trim()));
            } catch (NumberFormatException error) {
                System.out.println("數字格式錯誤");
            } catch (RuntimeException error) {
                System.out.println("其他執行例外");
            }
        }
    }
}
預期輸出
12
數字格式錯誤
其他執行例外

教材對照:PDF 第 6–10 頁

05|finally 與 return 的先後

finally 用於離開 try/catch 時的收尾。不論正常完成、例外或 return,通常都會先執行 finally。若 JVM 被終止、程序遭強制停止等,則不能保證它會執行。不要在 finally 寫 return 或拋出新例外,以免蓋掉原本結果。

JAVA 11 · Main.java程式碼
public class Main {
    static int calculate() {
        try {
            System.out.println("開始");
            return 7;
        } finally {
            System.out.println("收尾");
        }
    }

    public static void main(String[] args) {
        System.out.println(calculate());
    }
}
預期輸出
開始
收尾
7

教材對照:PDF 第 11–13 頁

06|Java 11 補充:自動關閉資源

檔案、串流等資源常需要 close。Java 11 的 try-with-resources 可管理 AutoCloseable 資源,在離開區塊時自動關閉,即使中途拋出例外也會處理。這是將教材 finally 收尾觀念用在 Java 11 的實用寫法。

JAVA 11 · Main.java程式碼
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner scanner = new Scanner("12 8")) {
            int total = scanner.nextInt() + scanner.nextInt();
            System.out.println(total);
        }
        System.out.println("Scanner 已關閉");
    }
}
預期輸出
20
Scanner 已關閉

教材對照:PDF 第 11–13(資源管理延伸) 頁

07|Call Stack:例外向呼叫者傳遞

目前方法沒有匹配的 catch 時,例外會沿著呼叫鏈向外傳遞,直到找到處理者。呼叫失敗後的敘述不會繼續執行;若最後仍未處理,該執行緒會終止並回報例外。閱讀 stack trace 時,先找到拋出處,再往上找呼叫來源。

JAVA 11 · Main.java程式碼
public class Main {
    static void readScore() {
        parseScore();
        System.out.println("此行不會執行");
    }
    static void parseScore() {
        Integer.parseInt("abc");
    }

    public static void main(String[] args) {
        try {
            readScore();
        } catch (NumberFormatException error) {
            System.out.println("main 收到格式錯誤");
        }
    }
}
預期輸出
main 收到格式錯誤

教材對照:PDF 第 14–15 頁

08|throw、throws 與自訂例外

throw 實際拋出一個例外物件;throws 寫在方法宣告中,告訴呼叫者可能向外傳遞哪些例外。自訂 checked exception 可繼承 Exception;訊息傳給 super(message),呼叫者就能使用 getMessage 取得。

JAVA 11 · Main.java程式碼
public class Main {
    static class InvalidScoreException extends Exception {
        InvalidScoreException(String message) {
            super(message);
        }
    }
    static void register(int score) throws InvalidScoreException {
        if (score < 0 || score > 100) {
            throw new InvalidScoreException("分數必須介於 0 到 100");
        }
        System.out.println("分數=" + score);
    }

    public static void main(String[] args) {
        try {
            register(-1);
        } catch (InvalidScoreException error) {
            System.out.println(error.getMessage());
        }
    }
}
預期輸出
分數必須介於 0 到 100

教材對照:PDF 第 16–19 頁

WHEN SOMETHING GOES WRONG

遇到錯誤,先檢查這裡

catch(Exception) 放在所有 catch 最前面

會遮住後面的子類別處理。先處理具體例外,再按需要處理較一般的例外。

以為 throws 就已經處理失敗

throws 讓責任往呼叫者傳遞;真正處理需要 catch 內的行為,或由更外層決定如何回應。

捕捉後什麼都不做,仍假裝成功

定義清楚失敗時應回傳、回報或停止的規則,保留必要訊息,不要讓錯誤結果繼續流入後續計算。

PAUSE AND TRY

先不要急著看別人的寫法

  1. 把第三節的 divisor 改成 4,哪些行會執行?
  2. 如果 finally 列印後,呼叫者才列印回傳值,輸出順序如何推導?
  3. 將 register(-1) 改成 register(100) 與 register(101),比較成功與失敗路線。

先用紙筆預測,再改動範例中的數字或文字。結果與預期不同時,找出最早開始不同的那一步。

把這幾件事帶走

  • 先讀例外類型,再決定處理方式。
  • catch 處理失敗,finally 收尾,throws 宣告傳遞。
  • 邊界檢查與例外處理各有用途,不能取代正確的程式邏輯。

FROM READING TO PRACTICE

用三道免費題,練習本章觀念

先做簡單題確認語法,再把幾個步驟組合起來。三題都來自本章主題的 Java 免費題庫,難度表示需要組合的基本概念。

教材來源與 Java 11 對照

本章依據《第二部分 04 Java 例外處理機制.pdf》PDF 第 2–19 頁整理,將原有觀念重新編寫為可閱讀與執行的網站教學。頁碼為 PDF 檔案頁次;舊版語法說明與已知誤植依 Java 11 修正,補充內容另有標示。

需要查閱更完整的規則時,可參考以下官方文件。