| 特性 | Exception (異常) | Error (錯誤) |
|---|---|---|
| 可處理性 | 開發者可捕獲、恢復、記錄 | 系統級故障,通常無法恢復 |
| 典型例子 | NullPointerException, IOException | OutOfMemoryError, StackOverflowError |
| 處理責任 | 業務代碼責任 | JVM/運行時環境責任 |
| 繼承鏈 | Throwable → Exception | Throwable → Error |
Throwable
├── Error // 嚴重系統錯誤,不建議捕獲
└── Exception // 可處理異常
├── RuntimeException // 運行時異常(非受檢)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ArithmeticException
│ └── ClassCastException
└── Checked Exception // 編譯時異常(受檢)
├── IOException
├── SQLException
├── ClassNotFoundException
└── InterruptedException
判斷標準:繼承 RuntimeException 或 Error 為非受檢異常,其餘為受檢異常。編譯器強制要求受檢異常必須 try-catch 或 throws 聲明。
try {
// 風險代碼
} catch (SpecificException e) {
// 處理特定異常
} catch (AnotherException e) {
// 多重捕獲
} finally {
// 清理代碼,無論是否異常都執行
}
try-catch ✓try-finally ✓try-catch-finally ✓catch-finally ✗(缺少 try)
// 多異常合併捕獲
try {
riskyOperation();
} catch (IOException | SQLException e) {
log.error("IO 或 SQL 異常", e);
throw new BusinessException(e);
}
// Try-with-resources 自動關閉實現 AutoCloseable 的資源
try (FileInputStream fis = new FileInputStream("data.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
return reader.readLine();
}
try {
parseConfig();
} catch (ParseException e) {
// 方式一:構造器傳入 cause (推薦)
throw new ConfigException("配置解析失敗", e);
// 方式二:initCause (僅能調用一次)
// ConfigException ce = new ConfigException("配置解析失敗");
// ce.initCause(e);
// throw ce;
}
效果:printStackTrace() 會顯示 Caused by: 鏈,完整保留根因信息。
// 底層異常 → 業務異常,隱藏實現細節
public User findUser(Long id) {
try {
return jdbcTemplate.queryForObject(sql, mapper, id);
} catch (EmptyResultDataAccessException e) {
throw new UserNotFoundException("用戶不存在: " + id, e);
} catch (DataAccessException e) {
throw new DataAccessFailureException("數據庫訪問失敗", e);
}
}
// 業務異常基類
public class BusinessException extends RuntimeException {
private final String errorCode;
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
}
// 具體業務異常
public class InsufficientBalanceException extends BusinessException {
public InsufficientBalanceException(String accountId, BigDecimal required) {
super("INSUFFICIENT_BALANCE",
String.format("賬戶 %s 餘額不足,需 %s", accountId, required));
}
}
public int trickyFinally() {
try {
return 1;
} finally {
return 2; // 覆蓋 try 的返回值!編譯器警告但合法
}
}
// 結果:返回 2
原則:finally 僅用於清理(關閉流、釋放鎖),嚴禁包含 return/throw/break/continue。
public String readFirstLine(String path) throws IOException {
// Java 7+ 推薦
try (BufferedReader br = Files.newBufferedReader(Paths.get(path))) {
return br.readLine();
}
// 自動調用 br.close(),即使 readLine() 拋異常
}
// 錯誤示範:吞沒異常
try {
saveData();
} catch (Exception e) {
log.error("保存失敗"); // 缺少 e,堆棧丟失!
}
// 正確:記錄完整堆棧或重新拋出
try {
saveData();
} catch (Exception e) {
log.error("保存失敗", e); // 參數順序:message, throwable
throw new RuntimeException("保存失敗", e);
}
class Parent {
void process() throws IOException {} // 拋出受檢異常
}
class Child extends Parent {
@Override
void process() throws FileNotFoundException {} // 合法:子類異常範圍 ≤ 父類
// void process() throws Exception {} // 編譯錯誤:範圍過大
// void process() {} // 合法:不拋出異常
}
@ControllerAdvice 集中處理,返回標準錯誤碼。