Java 例外處理 (Exception Handling)

例外處理用來處理程式執行時發生的錯誤,讓程式可以優雅地處理異常情況。

基本語法

使用 try-catch 捕捉例外:

try {
    // 可能發生例外的程式碼
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 處理例外
    System.out.println("發生錯誤:" + e.getMessage());
}

try-catch-finally

finally 區塊無論是否發生例外都會執行:

FileReader reader = null;
try {
    reader = new FileReader("file.txt");
    // 讀取檔案
} catch (FileNotFoundException e) {
    System.out.println("找不到檔案");
} finally {
    // 一定會執行,用於清理資源
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多重 catch

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[10]);
    int result = 10 / 0;
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("陣列索引超出範圍");
} catch (ArithmeticException e) {
    System.out.println("算術錯誤");
} catch (Exception e) {
    System.out.println("其他錯誤");
}

多重例外合併 (Java 7+)

try {
    // ...
} catch (IOException | SQLException e) {
    System.out.println("I/O 或資料庫錯誤:" + e.getMessage());
}

throw 和 throws

throw:拋出例外

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年齡不能為負數");
    }
    this.age = age;
}

throws:宣告方法可能拋出的例外

public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // ...
}

// 呼叫時必須處理
try {
    readFile("file.txt");
} catch (IOException e) {
    e.printStackTrace();
}

更多說明請參考 Java throws

例外類型

Throwable
├── Error(嚴重錯誤,通常不處理)
│   ├── OutOfMemoryError
│   └── StackOverflowError
└── Exception
    ├── RuntimeException(非受檢例外)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ArithmeticException
    │   └── IllegalArgumentException
    └── IOException(受檢例外)
        └── FileNotFoundException

受檢例外 vs 非受檢例外

類型說明處理
受檢例外 (Checked)編譯時期檢查必須 try-catch 或 throws
非受檢例外 (Unchecked)執行時期例外可選擇處理
// 受檢例外:必須處理
public void readFile() throws IOException {
    FileReader reader = new FileReader("file.txt");
}

// 非受檢例外:可以不處理(但會導致程式中斷)
public void divide(int a, int b) {
    int result = a / b;  // 可能拋出 ArithmeticException
}

try-with-resources (Java 7+)

自動關閉資源:

try (FileReader reader = new FileReader("file.txt");
     BufferedReader br = new BufferedReader(reader)) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}  // 自動關閉 reader 和 br

更多說明請參考 Java try-with-resources

常見例外

例外說明
NullPointerException存取 null 物件
ArrayIndexOutOfBoundsException陣列索引超出範圍
ArithmeticException算術錯誤(如除以零)
NumberFormatException數字格式錯誤
ClassCastException型別轉換錯誤
IllegalArgumentException非法參數
IOExceptionI/O 錯誤
FileNotFoundException找不到檔案

更多說明請參考 Java 常見例外

自訂例外

public class InsufficientBalanceException extends Exception {
    private double balance;
    private double amount;
    
    public InsufficientBalanceException(double balance, double amount) {
        super("餘額不足:餘額 " + balance + ",嘗試提領 " + amount);
        this.balance = balance;
        this.amount = amount;
    }
    
    public double getBalance() {
        return balance;
    }
    
    public double getAmount() {
        return amount;
    }
}

// 使用
public void withdraw(double amount) throws InsufficientBalanceException {
    if (amount > balance) {
        throw new InsufficientBalanceException(balance, amount);
    }
    balance -= amount;
}

更多說明請參考 Java 自訂例外

最佳實踐

  1. 具體的例外:捕捉具體的例外,而不是 Exception
  2. 有意義的訊息:提供有用的錯誤訊息
  3. 不要忽略例外:至少要記錄
  4. 早拋出、晚捕捉:盡早驗證,在適當的層級處理
// 不好
try {
    // ...
} catch (Exception e) {
    // 忽略
}

// 好
try {
    // ...
} catch (FileNotFoundException e) {
    logger.error("找不到設定檔", e);
    throw new ConfigurationException("無法載入設定", e);
}