異常處理是 Java 健壯性的基石。本文系統梳理異常分類體系、捕獲拋出模型、運行時與編譯時異常差異、自定義異常設計、子類重寫異常約束,並給出生產級最佳實踐指南。
java.lang.Throwable
├── java.lang.Error // 嚴重錯誤,應用無法恢復,不應捕獲
│ ├── StackOverflowError // 棧溢出:無限遞歸
│ ├── OutOfMemoryError // 內存溢出:堆/元空間耗盡
│ └── ...
└── java.lang.Exception // 異常,可捕獲處理
├── RuntimeException // 運行時異常 (非受檢異常)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ArithmeticException
│ ├── ClassCastException
│ └── ...
└── 其他 Exception 子類 // 編譯時異常 (受檢異常)
├── IOException
├── SQLException
├── ClassNotFoundException
└── ...
| 維度 | Error | RuntimeException | 受檢異常 |
|---|---|---|---|
| 可恢復性 | 不可恢復 | 邏輯錯誤,可通過代碼修正避免 | 外部因素,必須處理 |
| 編譯器強制 | 否 | 否 | 是 (try-catch 或 throws) |
| 典型場景 | JVM 級故障 | 空指針、越界、除零、類型轉換 | IO、網絡、數據庫、反射 |
// 1. 數組越界
int[] arr = new int[10];
System.out.println(arr[10]); // ArrayIndexOutOfBoundsException: 10
// 2. 算術異常
int i = 10 / 0; // ArithmeticException: / by zero
// 3. 類型轉換異常
Object obj = new Date();
String str = (String) obj; // ClassCastException
// 4. 空指針異常
String s = "abc";
s = null;
s.toString(); // NullPointerException
// 5. 編譯時異常:文件讀取
FileInputStream fis = new FileInputStream(new File("Hello.txt")); // 必須處理 IOException
try {
// 可能拋異常的代碼
FileInputStream fis = new FileInputStream("test.txt");
} catch (FileNotFoundException e) {
// 具體異常處理
e.printStackTrace();
} catch (IOException e) {
// 父類異常放後面
e.printStackTrace();
} finally {
// 必執行代碼:資源關閉、鎖釋放
// 即使 try/catch 中有 return 也會執行
}
try 內變量作用域僅限 try 塊finally 可選,但一定執行 (除非 System.exit()、JVM 崩潰)catch 由上至下匹配,子類在前,父類在後getMessage()、printStackTrace()try-catch 可嵌套public static void readFile() throws IOException {
FileInputStream fis = new FileInputStream("test.txt");
// 異常自動向上拋給調用者
}
throws 後可聲明多個異常,用逗號分隔main,中途可隨時 try-catch 攔截處理| 類型 | 處理建議 | 原因 |
|---|---|---|
| 運行時異常 | 可不顯式處理,通過代碼邏輯預防 (非空判斷、邊界檢查) | 屬於編程錯誤,修正代碼即可消除 |
| 編譯時異常 | 必須處理 (try-catch 或 throws) | 外部不可控因素,強制開發者面對 |
public class BusinessException extends RuntimeException {
// 1. 序列化版本號
private static final long serialVersionUID = -7034897190745766939L;
// 2. 業務錯誤碼 (便於前端/監控識別)
private final int errorCode;
// 3. 構造器鏈
public BusinessException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public BusinessException(int errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public int getErrorCode() { return errorCode; }
}
RuntimeException (非受檢),避免污染方法簽名Exception (受檢),強制感知cause)鐵律:子類重寫方法拋出的異常只能是父類方法聲明異常的子類或相同異常,即拋出範圍只能縮小,不能擴大。
class Parent {
void method() throws IOException {} // 拋出 IOException
}
class Child extends Parent {
@Override
void method() throws FileNotFoundException {} // ✅ 合法:FileNotFoundException 繼承 IOException
// void method() throws Exception {} // ❌ 非法:Exception 是 IOException 父類,範圍擴大
}
message 面向開發者,errorCode 面向系統,cause 保留原始堆棧catch (Exception e) {} 絕對禁止,至少記錄日誌AutoCloseable 的資源@ControllerAdvice 統一捕獲、記錄、返回標準錯誤響應log.error("業務描述", e) 而非 log.error(e.getMessage()),保留完整堆棧Java 異常機制通過拋出-捕獲-處理模型,將錯誤檢測與錯誤處理解耦。掌握異常分類、正確選擇處理方式、設計清晰的異常體系,是編寫健壯、可維護 Java 代碼的核心技能。