Java 常見例外

了解常見的例外類型可以幫助你更好地處理和預防程式錯誤。

例外分類

Throwable
├── Error(系統錯誤,不應捕捉)
│   ├── OutOfMemoryError
│   ├── StackOverflowError
│   └── ...
└── Exception
    ├── RuntimeException(Unchecked Exception)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── ...
    └── 其他 Exception(Checked Exception)
        ├── IOException
        ├── SQLException
        └── ...

Unchecked Exception

不需要強制處理,通常是程式邏輯錯誤。

NullPointerException

存取 null 物件的成員:

String s = null;
int length = s.length();  // NullPointerException

// 預防方式
if (s != null) {
    int length = s.length();
}

// 或使用 Optional
Optional.ofNullable(s).ifPresent(str -> {
    System.out.println(str.length());
});

// Java 14+ 顯示更詳細的訊息
// Cannot invoke "String.length()" because "s" is null

ArrayIndexOutOfBoundsException

陣列索引超出範圍:

int[] arr = {1, 2, 3};
int value = arr[5];  // ArrayIndexOutOfBoundsException

// 預防方式
if (index >= 0 && index < arr.length) {
    int value = arr[index];
}

StringIndexOutOfBoundsException

字串索引超出範圍:

String s = "Hello";
char c = s.charAt(10);  // StringIndexOutOfBoundsException

// 預防方式
if (index >= 0 && index < s.length()) {
    char c = s.charAt(index);
}

IndexOutOfBoundsException

集合索引超出範圍:

List<String> list = Arrays.asList("a", "b", "c");
String s = list.get(10);  // IndexOutOfBoundsException

ClassCastException

無效的型別轉換:

Object obj = "Hello";
Integer num = (Integer) obj;  // ClassCastException

// 預防方式
if (obj instanceof Integer) {
    Integer num = (Integer) obj;
}

// Java 16+ pattern matching
if (obj instanceof Integer num) {
    System.out.println(num);
}

IllegalArgumentException

非法參數:

public void setAge(int age) {
    if (age < 0 || age > 150) {
        throw new IllegalArgumentException("年齡必須在 0-150 之間");
    }
    this.age = age;
}

IllegalStateException

非法狀態:

public class Connection {
    private boolean connected = false;

    public void sendData(String data) {
        if (!connected) {
            throw new IllegalStateException("尚未連線");
        }
        // 發送資料
    }
}

ArithmeticException

算術錯誤:

int result = 10 / 0;  // ArithmeticException

// 預防方式
if (divisor != 0) {
    int result = 10 / divisor;
}

NumberFormatException

數字格式錯誤:

int num = Integer.parseInt("abc");  // NumberFormatException

// 預防方式
public static Integer parseIntSafe(String s) {
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        return null;
    }
}

UnsupportedOperationException

不支援的操作:

List<String> list = Arrays.asList("a", "b", "c");
list.add("d");  // UnsupportedOperationException(固定大小的 List)

// 解決方式
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
list.add("d");  // OK

ConcurrentModificationException

並發修改錯誤:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
    list.remove(s);  // ConcurrentModificationException
}

// 解決方式:使用 Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    it.remove();  // OK
}

// 或使用 removeIf
list.removeIf(s -> s.equals("a"));

Checked Exception

必須處理或宣告的例外。

IOException

I/O 操作錯誤:

try {
    FileReader reader = new FileReader("file.txt");
} catch (IOException e) {
    System.out.println("檔案讀取錯誤:" + e.getMessage());
}

FileNotFoundException

檔案不存在:

try {
    FileInputStream fis = new FileInputStream("nonexistent.txt");
} catch (FileNotFoundException e) {
    System.out.println("檔案不存在");
}

SQLException

資料庫操作錯誤:

try {
    Connection conn = DriverManager.getConnection(url, user, password);
    Statement stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
} catch (SQLException e) {
    System.out.println("資料庫錯誤:" + e.getMessage());
}

ParseException

解析錯誤:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
    Date date = sdf.parse("invalid-date");
} catch (ParseException e) {
    System.out.println("日期格式錯誤");
}

InterruptedException

執行緒中斷:

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // 重新設定中斷狀態
    System.out.println("執行緒被中斷");
}

ClassNotFoundException

類別找不到:

try {
    Class.forName("com.example.NonExistentClass");
} catch (ClassNotFoundException e) {
    System.out.println("類別不存在");
}

Error(系統錯誤)

通常不應該捕捉,表示嚴重的系統問題。

OutOfMemoryError

記憶體不足:

// 不建議捕捉,但可以這樣預防
try {
    byte[] hugeArray = new byte[Integer.MAX_VALUE];
} catch (OutOfMemoryError e) {
    System.out.println("記憶體不足");
    // 清理資源
}

StackOverflowError

堆疊溢位(通常是無限遞迴):

public void infiniteRecursion() {
    infiniteRecursion();  // StackOverflowError
}

例外處理最佳實踐

1. 精確捕捉

// ✗ 太籠統
try {
    // ...
} catch (Exception e) {
    // ...
}

// ✓ 精確捕捉
try {
    // ...
} catch (FileNotFoundException e) {
    // 處理檔案不存在
} catch (IOException e) {
    // 處理其他 I/O 錯誤
}

2. 不要忽略例外

// ✗ 忽略例外
try {
    // ...
} catch (Exception e) {
    // 什麼都不做
}

// ✓ 至少記錄
try {
    // ...
} catch (Exception e) {
    logger.error("發生錯誤", e);
}

3. 提供有意義的訊息

throw new IllegalArgumentException("使用者 ID 必須大於 0,實際值:" + userId);

4. 在適當的層級處理

// 低層級:拋出技術性例外
public byte[] readFile(String path) throws IOException {
    return Files.readAllBytes(Paths.get(path));
}

// 高層級:轉換為業務例外或處理
public Config loadConfig() {
    try {
        byte[] data = readFile("config.json");
        return parseConfig(data);
    } catch (IOException e) {
        throw new ConfigurationException("無法載入設定檔", e);
    }
}

常見例外速查表

例外說明類型
NullPointerException存取 null 物件Unchecked
ArrayIndexOutOfBoundsException陣列索引越界Unchecked
ClassCastException型別轉換錯誤Unchecked
IllegalArgumentException非法參數Unchecked
IllegalStateException非法狀態Unchecked
ArithmeticException算術錯誤Unchecked
NumberFormatException數字格式錯誤Unchecked
IOExceptionI/O 錯誤Checked
FileNotFoundException檔案不存在Checked
SQLException資料庫錯誤Checked
ParseException解析錯誤Checked
InterruptedException執行緒中斷Checked

重點整理

  • Unchecked Exception:程式邏輯錯誤,不需要強制處理
  • Checked Exception:外部因素導致,必須處理或宣告
  • Error:系統級錯誤,通常不應捕捉
  • 精確捕捉例外,不要使用過於籠統的 catch
  • 不要忽略例外,至少要記錄
  • 在適當的層級處理例外