本文通過一道綜合練習題,演示 Java 核心運行時異常的捕獲與處理:ArithmeticException、NullPointerException、ArrayIndexOutOfBoundsException、StringIndexOutOfBoundsException,並補充自定義異常在銀行取款場景中的應用。
public class ExceptionHandlingDemo {
public static void main(String[] args) {
for (int i = 0; i < 4; i++) {
int k = 0;
switch (i) {
case 0: // 算術異常:除零
int zero = 0;
try {
k = 911 / zero;
} catch (ArithmeticException e) {
System.out.println("發生算術異常:" + e.getMessage());
}
break;
case 1: // 空指針異常:null 數組訪問
try {
int[] b = null;
k = b[0];
} catch (NullPointerException e) {
System.out.println("發生空指針異常:" + e.getMessage());
}
break;
case 2: // 數組下標越界
int[] c = new int[2];
try {
k = c[9];
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("發生數組下標越界:" + e.getMessage());
}
break;
case 3: // 字符串下標越界
try {
char ch = "abc".charAt(99);
} catch (StringIndexOutOfBoundsException e) {
System.out.println("發生字符串下標越界:" + e.getMessage());
}
break;
}
}
}
}
輸出:
發生算術異常:/ by zero
發生空指針異常:null
發生數組下標越界:Index 9 out of bounds for length 2
發生字符串下標越界:String index out of range: 99
Throwable
├── Error (嚴重錯誤,不可恢復:OOM, StackOverflow)
└── Exception
├── RuntimeException (非檢查異常,編譯器不強制捕獲)
│ ├── ArithmeticException
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── StringIndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── ...
└── IOException 等 (檢查異常,編譯器強制 try-catch 或 throws)
// 餘額不足異常
public class InsufficientFundsException extends Exception {
public InsufficientFundsException(String message) { super(message); }
public InsufficientFundsException(String message, Throwable cause) { super(message, cause); }
}
// 取款金額非法異常 (負數或零)
public class InvalidAmountException extends Exception {
public InvalidAmountException(String message) { super(message); }
}
public class BankAccount {
private double balance;
public BankAccount(double initialBalance) {
if (initialBalance < 0) throw new IllegalArgumentException("初始餘額不能為負");
this.balance = initialBalance;
}
public double getBalance() { return balance; }
/** 取款:業務異常拋出檢查異常,強制調用者處理 */
public void withdraw(double amount) throws InsufficientFundsException, InvalidAmountException {
if (amount <= 0) {
throw new InvalidAmountException("取款金額必須大於零,實際:" + amount);
}
if (amount > balance) {
throw new InsufficientFundsException(
String.format("餘額不足:當前 %.2f,請求 %.2f", balance, amount)
);
}
balance -= amount;
System.out.printf("取款成功:%.2f,剩餘餘額:%.2f%n", amount, balance);
}
public void deposit(double amount) {
if (amount <= 0) throw new InvalidAmountException("存款金額必須大於零");
balance += amount;
}
}
public class BankTest {
public static void main(String[] args) {
BankAccount account = new BankAccount(100.00);
System.out.println("初始餘額:" + account.getBalance());
// 正常取款
try {
account.withdraw(30);
} catch (InsufficientFundsException | InvalidAmountException e) {
System.err.println("業務異常:" + e.getMessage());
}
// 餘額不足
try {
account.withdraw(150);
} catch (InsufficientFundsException e) {
System.err.println("捕獲:"+ e.getMessage());
} catch (InvalidAmountException e) {
System.err.println("捕獲:"+ e.getMessage());
}
// 非法金額 (負數)
try {
account.withdraw(-15);
} catch (InvalidAmountException e) {
System.err.println("捕獲:" + e.getMessage());
}
// 多異常合併捕獲 (Java 7+)
try {
account.withdraw(200);
} catch (InsufficientFundsException | InvalidAmountException e) {
System.err.println("合併捕獲:" + e.getClass().getSimpleName() + " - " + e.getMessage());
}
}
}
| 原則 | 說明 | 反例 | 正例 |
|---|---|---|---|
| 具體捕獲 | 捕獲具體異常類,避免 catch (Exception) | catch (Exception e) | catch (IOException \| SQLException e) |
| 業務異常用檢查異常 | 餘額不足、參數非法等預期內業務錯誤 | 拋 RuntimeException | 拋 InsufficientFundsException (extends Exception) |
| 系統異常用非檢查異常 | Bug、環境故障、不可恢復 | 拋 checked exception | 拋 IllegalStateException / 讓 NPE 拋出 |
| 異常鏈保留 | 拋新異常時附帶 cause | throw new BizEx("msg") | throw new BizEx("msg", rootCause) |
| 不吞噬異常 | catch 後必須處理、記錄、或重拋 | catch (Ex e) {} | log.error("msg", e); throw new BizEx(e); |
| finally 釋放資源 | 流、連接、鎖、信號量 | 手動 close | try-with-resources |
| 避免異常控制流程 | 異常僅用於錯誤,不用於正常判斷 | try { map.get(k) } catch { return default } | map.getOrDefault(k, default) |
// 多異常合併捕獲 (異常類無繼承關係)
try {
risky();
} catch (IOException | SQLException | TimeoutException e) {
log.error("資源訪問失敗", e);
}
// try-with-resources 自動關閉 (實現 AutoCloseable)
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 使用資源
} // 編譯器自動生成 finally 關閉代碼,即使拋異常也會關閉
ArithmeticException、NullPointerException、ArrayIndexOutOfBoundsException、StringIndexOutOfBoundsException) 屬非檢查異常,**編譯器不強制捕獲**,但生產代碼關鍵路徑建議顯式防護。try-with-resources、多異常合併捕獲、異常鏈保留,編寫健壯、可維護的異常處理代碼。