異常是 Java 程序運行過程中發生的非預期事件,干擾正常指令流。本文系統梳理異常繼承體系、分類邊界、JVM 默認處理、捕獲與拋出機制、finally 語義、自定義異常、重寫規則、選型原則,並針對面試高頻題(final/finally/finalize、catch 中 return 執行順序)深度解析,構建完整異常處理知識網。
java.lang.Object
└── java.lang.Throwable (所有異常/錯誤的祖先)
├── java.lang.Error (嚴重錯誤,程序無法處理,JVM 層面)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── NoClassDefFoundError
└── java.lang.Exception (程序可處理的異常)
├── java.lang.RuntimeException (運行時異常,不可查異常)
│ ├── NullPointerException
│ ├── IndexOutOfBoundsException
│ ├── IllegalArgumentException
│ └── ClassCastException
└── 其他 Exception 子類 (受檢異常,可查異常)
├── IOException
├── SQLException
├── ClassNotFoundException
└── 用戶自定義 Exception
| 維度 | Error | RuntimeException 及子類 | 其他 Exception 子類 |
|---|---|---|---|
| 別名 | 錯誤 | 不可查異常 / 非受檢異常 | 可查異常 / 受檢異常 |
| 來源 | JVM/系統級故障 | 邏輯錯誤/編程疏忽 | 外部環境/業務邏輯可預期 |
| 編譯器強制 | 不檢查 | 不檢查 | 強制 try-catch 或 throws |
| 典型例子 | OOM、棧溢出 | NPE、越界、類轉換 | IO、SQL、類加載失敗 |
| 處理策略 | 監控告警、優化代碼/配置、重啟 | 修復代碼邏輯、防御性編程 | 捕獲處理/向上拋出、業務補償 |
main 方法仍無處理 → 交給 JVM 默認處理器。try {
// 可能拋異常的代碼
int a = 10 / 0;
} catch (ArithmeticException e) {
// 捕獲具體異常類型
System.out.println("除零錯誤:" + e.getMessage());
} catch (Exception e) {
// 捕獲父類異常(放最後,否則吞噬子類)
e.printStackTrace();
} finally {
// 無論是否異常、是否 return,必執行(除 System.exit(0))
// 釋放資源:流關閉、鎖釋放、連接歸還
}
catch (IOException | SQLException e),e 隱含 final,不可賦值。return,finally 仍在返回前執行。public void readFile(String path) throws IOException {
Files.readAllLines(Paths.get(path)); // 拋出受檢異常
}
catch { } 空體),否則丟失棧跡,排查極難。| 維度 | throw | throws |
|---|---|---|
| 位置 | 方法體內部 | 方法簽名聲明處 |
| 後接 | 異常對象實例 new Exception() | 異常類名,可多個用逗號隔開 | 語義 | 主動拋出異常,轉移控制權 | 聲明可能拋出異常,告知調用者 |
| 數量 | 單個對象 | 多個類 |
String getMessage() // 僅獲取異常信息(如 "/ by zero")
String toString() // 類名 + 信息("java.lang.ArithmeticException: / by zero")
void printStackTrace() // 類名 + 信息 + 完整棧跡(行號、方法調用鏈),輸出到標準錯誤流
// 重定向輸出:e.printStackTrace(new PrintWriter(sw))
System.exit(0)、JVM 崩潰、死循環、線程被 kill(Thread.stop() 已棄用)。
public int test() {
try {
return 1;
} catch (Exception e) {
return 2;
} finally {
System.out.println("finally");
return 3; // 覆蓋前面的返回值!
}
}
// 輸出:finally
// 返回值:3
結論:finally 中的 return/throw 會覆蓋 try/catch 中的返回值/異常,導致異常丟失。生產代碼嚴禁在 finally 中使用 return/throw,僅做資源清理。
| 關鍵字 | 作用 | 典型場景 |
|---|---|---|
| final | 修飾類/方法/變量:不可繼承/重寫/重賦值 | 常量、不可變類、模板方法 |
| finally | 異常處理塊:必執行清理邏輯 | 關流、釋放鎖、歸還連接 |
| finalize | Object 方法:GC 前調用(不可靠,JDK 9 棄用) | 歷史:釋放原生資源,現用 try-with-resources/Cleaner |
InsufficientBalanceException、UserNotFoundException)。
// 1. 繼承 Exception → 受檢異常,調用者強制處理,適合業務預期錯誤
public class BusinessException extends Exception {
private int errorCode;
public BusinessException(String msg, int code) { super(msg); this.errorCode = code; }
}
// 2. 繼承 RuntimeException → 非受檢異常,調用者可選處理,適合系統/編程錯誤
public class SystemException extends RuntimeException {
public SystemException(String msg, Throwable cause) { super(msg, cause); }
}
Serializable(分布式傳輸需求)。Exception 結尾,語義清晰。記憶口訣:子類不能比父類「更壞」(拋出更多受檢異常)。
發現異常
│
├─ 能在當前方法有意義地處理?(記錄日誌、重試、降級、轉碼返回)
│ ├─ 是 → try-catch 處理,後續代碼繼續執行
│ └─ 否 → throws 向上拋出
│
├─ 是否為受檢異常?
│ ├─ 是 → 必須 try-catch 或 throws
│ └─ 否 → 可選處理,建議關鍵邏輯防御性捕獲
│
├─ 是否需要自定義異常?
│ ├─ 業務語義獨特 → 繼承 Exception (受檢)
│ └─ 系統/編程錯誤 → 繼承 RuntimeException
│
└─ 最終頂層入口(main/Controller/線程池)統一捕獲,記錄完整棧跡,返回友好錯誤碼
Java 異常機制以 Throwable 為根,Error/Exception 分治,受檢/非受檢強制分流,構建了編譯期靜態約束 + 運行期動態傳播的雙重保障體系。掌握 try-catch-finally 執行順序、throws 傳遞鏈、自定義異常語義分層、重寫約束、finally 禁 return 等核心細節,能寫出健壯、可維護、可排查的異常處理代碼。記住:異常是為了「被處理」而存在的,不是為了「被吞掉」——每一行 catch 都應有明確業務意圖。