程序運行過程中,內存不足、網絡斷開、空指針調用、數據格式錯誤等「非正常情況」層出不窮。Java 通過**異常機制**統一處理這些偏離主流程的情況。本文從異常對象創建、拋出、捕獲、棧追蹤生成的底層流程切入,深度解析 Throwable 體系架構、checked/unchecked 分類邏輯、自定義異常最佳實踐,幫你建立「異常即控制流」的系統性認知。
public class ExceptionTest {
public static void main(String[] args) {
String s = null;
s.indexOf("a"); // 此處拋出 NPE
System.out.println("end"); // 不會執行
}
}
控制台輸出:
Exception in thread "main" java.lang.NullPointerException
at ExceptionTest.main(ExceptionTest.java:5)
流程還原:
s == null,無法調用實例方法NullPointerException 對象,調用 fillInStackTrace() 記錄棧幀catch 塊System.err → 線程終止System.out.println("end") 永不執行
public class ExceptionTest {
public static void main(String[] args) {
if (args.length < 1) { System.out.println("請輸入數字"); return; }
int num = Integer.parseInt(args[0]); // 用戶輸 "abc" 觸發
System.out.println(num);
}
}
源碼級追蹤:
// Integer.parseInt 內部
int digit = Character.digit(s.charAt(i++), radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s); // 關鍵:throw 關鍵字主動拋出
}
// NumberFormatException.forInputString
static NumberFormatException forInputString(String s) {
return new NumberFormatException("For input string: \"" + s + "\"");
}
關鍵對比:
- return:正常退出,返回值確定傳給調用者
- throw:**異常退出**,異常對象沿調用棧「冒泡」,由運行時動態決定誰捕獲
try {
int num = Integer.parseInt(args[0]);
System.out.println(num);
} catch (NumberFormatException e) {
System.err.println("參數 " + args[0] + " 不是有效數字,請輸入數字");
}
// catch 執行後,程序繼續執行後續代碼(而非退出)
java.lang.Throwable (所有異常/錯誤根類)
├── java.lang.Error // 系統級錯誤,應用不應捕獲/拋出
│ ├── VirtualMachineError
│ │ ├── OutOfMemoryError // 堆/元空間耗盡
│ │ └── StackOverflowError // 遞歸過深/死循環
│ ├── AWTError
│ └── ...
└── java.lang.Exception // 應用級異常,可捕獲處理
├── RuntimeException // 未受檢異常
│ ├── NullPointerException
│ ├── IllegalArgumentException
│ ├── ClassCastException
│ ├── IndexOutOfBoundsException
│ │ ├── ArrayIndexOutOfBoundsException
│ │ └── StringIndexOutOfBoundsException
│ ├── NumberFormatException
│ └── ...
├── IOException // 受檢異常
├── SQLException
├── ClassNotFoundException
└── ... (所有非 RuntimeException 子類)

public class Throwable implements Serializable {
// 四大構造器
public Throwable()
public Throwable(String message)
public Throwable(String message, Throwable cause) // 異常鏈支持
public Throwable(Throwable cause)
// 關鍵:填充棧追蹤,構造器自動調用
private native Throwable fillInStackTrace();
// 設置 cause(僅能調用一次)
public Throwable initCause(Throwable cause)
// 常用方法
public String getMessage() // 獲取構造器傳入的 message
public Throwable getCause() // 獲取底層原因
public void printStackTrace() // 打印到 System.err
public void printStackTrace(PrintStream s) // 打印到指定流
public void printStackTrace(PrintWriter s)
public StackTraceElement[] getStackTrace() // 程序化訪問棧幀
}
StackTraceElement 包含:類名、方法名、文件名、行號、是否本地方法。
| 分類 | 代表 | 編譯器要求 | 處理策略 | 典型場景 |
|---|---|---|---|---|
| Checked Exception (受檢異常) | IOException, SQLException, ClassNotFoundException, 所有非 RuntimeException 的 Exception 子類 | **強制**:方法簽名必須 throws 或 try-catch 捕獲,否則編譯錯誤 | 調用者**必須**處理,通常業務邏輯可預期、可恢復 | 文件讀寫、數據庫操作、網絡請求、反射加載類 |
| Unchecked Exception (未受檢異常) | RuntimeException 及其子類、Error 及其子類 | **不強制**:可拋可不拋,編譯器不檢查 | 通常屬於**編程錯誤**,應修復代碼而非捕獲;Error 屬系統崩潰,不可恢復 | 空指針、數組越界、類型轉換錯誤、參數非法、OOM、StackOverflow |
設計哲學:Checked 強制 API 使用者感知「可能失敗」;Unchecked 留給開發者通過測試/Code Review 保證正確性。
public class AppException extends Exception { // 繼承 Exception → Checked
public AppException() { super(); }
public AppException(String message) { super(message); }
public AppException(String message, Throwable cause) { super(message, cause); }
public AppException(Throwable cause) { super(cause); }
}
// 使用示例
public void transfer(Account from, Account to, BigDecimal amount) {
if (from.getBalance().compareTo(amount) < 0) {
throw new AppException("餘額不足", new InsufficientFundsException());
}
// ...
}
決策樹:
Exception (Checked)RuntimeException (Unchecked)Error (極少自定義)
try {
// 底層操作
Files.readAllLines(path);
} catch (IOException e) {
// 包裝拋出,保留原始棧追蹤
throw new AppException("讀取配置文件失敗", e); // cause 鏈保持完整
}
打印棧追蹤時會顯示 Caused by: ...,完整保留錯誤發生現場,便於排查。
throw 觸發,try-catch 捕獲,Throwable 為根類throw new XxxException()fillInStackTrace() 是棧追蹤生成關鍵,性能開銷大,**避免在高頻路徑用異常控制流程**cause下一節將深入講解 try-catch-finally 執行語義、try-with-resources 自動資源管理、異常處理反模式與性能優化。敬請關注公眾號「老馬說編程」獲取最新內容。
