Java 異常機制是程式健壯性的基石。RuntimeException (非受檢異常) 及其子類佔據了開發中 80% 以上的異常場景。本文系統梳理異常體系架構、核心概念、常見 RuntimeException 成因與處理策略,助你寫出更可靠的代碼。
java.lang.Throwable
├── java.lang.Error // 嚴重錯誤 (JVM 層面,不可恢復)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── NoClassDefFoundError
└── java.lang.Exception // 一般異常
├── java.lang.RuntimeException // ★ 非受檢異常 (Unchecked)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ ├── IllegalArgumentException
│ ├── NumberFormatException
│ ├── ArithmeticException
│ ├── IndexOutOfBoundsException
│ ├── IllegalStateException
│ ├── UnsupportedOperationException
│ └── ... (邏輯/程式設計錯誤)
└── 其他 Exception 子類 // 受檢異常 (Checked)
├── IOException
├── SQLException
├── ClassNotFoundException
├── InterruptedException
└── ... (環境/外部資源錯誤)
| 概念 | 說明 | 代表類別 | 處理策略 |
|---|---|---|---|
| Error | JVM 層面嚴重問題,應用無法恢復 | OOM, StackOverflow | 僅能記錄日誌、優雅退出,不可 try-catch 恢復 |
| RuntimeException | **程式設計邏輯錯誤**,可透過防禦性程式設計避免 | NPE, 索引越界, 類型轉換 | **修正代碼邏輯**,而非單純 catch 處理 |
| Checked Exception | 外部環境不確定性 (IO、網路、資料庫),**必須**處理 | IOException, SQLException | try-catch 處理、拋出、轉換為業務異常 |
logic_error (邏輯錯誤) ≈ Java RuntimeException;runtime_error (執行時環境錯誤) ≈ Java Checked Exception。RuntimeException 易誤導:所有異常皆在運行時拋出。實質區分在於 **「是否為程式設計錯誤」**。// 典型成因 String s = null; s.length(); // 💥 Listlist = new ArrayList<>(); list.add(null); list.get(0).toString(); // 💥 元素為 null // 防禦策略 // ✅ 使用 Objects.requireNonNull (Java 7+) public void setName(String name) { this.name = Objects.requireNonNull(name, "姓名不可為空"); } // ✅ Optional 避免顯式 null 檢查 (Java 8+) public String getNameOrDefault(User user) { return Optional.ofNullable(user) .map(User::getName) .orElse("匿名"); } // ✅ 註解標記 @NonNull/@Nullable (IDE/靜態分析工具支援)
// 成因 int[] arr = new int[3]; arr[3] = 10; // 💥 索引 0~2 Listlist = Arrays.asList("a", "b"); list.get(2); // 💥 // 防禦策略 // ✅ 使用增強 for / Stream 避免手動索引 for (String s : list) { ... } list.forEach(s -> ...); // ✅ 必須用索引時,先檢查 bounds if (index >= 0 && index < arr.length) { ... } // ✅ 使用 List.getOrDefault (Java 9+) / 手動封裝
// 成因
Object obj = "hello";
Integer num = (Integer) obj; // 💥 String 不能轉 Integer
List list = new ArrayList(); // 原始類型
list.add("string");
Integer i = (Integer) list.get(0); // 💥 執行時才發現
// 防禦策略
// ✅ 使用泛型 杜絕原始類型
List list = new ArrayList<>();
list.add("string");
// String s = list.get(0); // 編譯期保證類型安全
// ✅ instanceof 檢查後再轉型
if (obj instanceof Integer) {
Integer num = (Integer) obj;
}
// ✅ 模式匹配 instanceof (Java 16+)
if (obj instanceof Integer num) { // 直接宣告變數
num.intValue();
}
// 區別
// IllegalArgumentException: 方法參數不合法 (前置條件違反)
public void setAge(int age) {
if (age < 0 || age > 150)
throw new IllegalArgumentException("年齡範圍 0-150: " + age);
this.age = age;
}
// IllegalStateException: 物件狀態不合法 (方法調用時機錯誤)
public void startEngine() {
if (this.status != Status.STOPPED)
throw new IllegalStateException("引擎已啟動,不可重複啟動");
this.status = Status.RUNNING;
}
// 成因
Integer.parseInt("123abc"); // 💥
Double.parseDouble("3.14.15"); // 💥
// 防禦策略
// ✅ 使用正則預驗證 / 嘗試解析封裝
public static Integer parseIntSafe(String s) {
try {
return Integer.parseInt(s.trim());
} catch (NumberFormatException e) {
return null; // 或拋出業務異常
}
}
// ✅ Java 11+ NumberFormatException 訊息更詳細,可直接記錄
int a = 10 / 0; // 💥 整數除零 // 浮點數除零不拋異常,返回 Infinity/NaN double d = 10.0 / 0.0; // Infinity
// ✅ 正確:資料層捕獲 SQLException,轉換為業務異常拋出
public User findById(String id) {
try {
return jdbc.queryForObject(sql, rowMapper, id);
} catch (EmptyResultDataAccessException e) {
return null; // 正常業務情況:查無資料
} catch (SQLException e) {
throw new DataAccessException("查詢用戶失敗", e); // 包裝拋出
}
}
// ❌ 錯誤:吞噬異常
try { ... } catch (Exception e) {
e.printStackTrace(); // 僅印堆疊,調用者不知失敗
return null;
}
// ✅ Java 7+ try-with-resources (自動關閉實現 AutoCloseable)
try (Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 使用資源
} // 自動關閉 rs, ps, conn (即使拋異常)
// ✅ 建構子傳入 cause
throw new BusinessException("訂單建立失敗", originalException);
// ❌ 丟失堆疊
throw new BusinessException("訂單建立失敗"); // 原因丟失
// ❌ 反模式:用異常做流程控制
try {
while (true) list.get(index++);
} catch (IndexOutOfBoundsException e) {
// 結束迴圈
}
// ✅ 正確:用條件判斷
while (index < list.size()) { ... }
RuntimeException (業務邏輯錯誤) 或 Exception (受檢業務異常)。InsufficientBalanceException),避免過度設計異常類別結構。String message 建構子,可選 Throwable cause。| 異常 | 典型成因 | 預防關鍵字 |
|---|---|---|
| NullPointerException | 呼叫 null 物件方法/欄位 | Objects.requireNonNull, Optional, @NonNull |
| ArrayIndexOutOfBoundsException | 陣列/List 索引超出範圍 | 增強 for, Stream, 邊界檢查 |
| ClassCastException | 強制轉型失敗 | 泛型, instanceof, 模式匹配 |
| IllegalArgumentException | 參數值不合法 | 前置條件檢查, 驗證框架 |
| NumberFormatException | 字串解析數字失敗 | 正則驗證, 安全解析工具 |
| IllegalStateException | 物件狀態不允許操作 | 狀態機設計, 狀態檢查 |
| ConcurrentModificationException | 迭代中修改集合 | Iterator.remove, CopyOnWriteList, Stream |
| UnsupportedOperationException | 不可修改集合調用 add/remove | List.of, Collections.unmodifiableList |
@ControllerAdvice + @ExceptionHandler 統一轉換為標準錯誤回應 (JSON)。log.error("業務失敗", e) 保留完整堆疊,**避免** e.printStackTrace()。traceId,貫穿日誌、異常、回應頭,便於鏈路追蹤。RuntimeException 本質是**程式設計缺陷**的體現。掌握體系架構、熟知高頻異常成因、奉行「防禦性程式設計」與「精準異常處理」原則,才能從根源減少線上故障,提升系統穩定性。**記住:最好的異常處理,是寫出不拋異常的代碼。**