異常處理是 Java 健壯性的基石。throw 與 throws 雖只一字之差,但語義、位置、職責截然不同。本文通過對比實驗、源碼級分析、最佳實踐,徹底釐清兩者關係。
// 除零
int a = 5, b = 0; System.out.println(a / b);
// ArithmeticException: / by zero
// 類型轉換
String s = "abc"; Double.parseDouble(s);
// NumberFormatException: For input string: "abc"
public static void main(String[] args) {
String s = "abc";
if (s.equals("abc")) {
throw new NumberFormatException("業務邏輯禁止 abc");
}
}
// NumberFormatException: 業務邏輯禁止 abc
public static void risky() throws NumberFormatException {
Double.parseDouble("abc"); // 內部拋出,不捕獲,聲明拋出
}
public static void main(String[] args) {
try { risky(); }
catch (NumberFormatException e) { System.err.println("非數據類型"); }
}
// 輸出:非數據類型
| 維度 | throw | throws |
|---|---|---|
| 出現位置 | 方法體內部語句 | 方法聲明簽名 |
| 語義 | 實際拋出異常對象 | 聲明可能拋出的異常類型 |
| 確定性 | 執行必拋出 | 運行時不一定發生 |
| 異常數量 | 單個對象 | 可聲明多個,逗號分隔 |
| 處理責任 | 當前方法或調用鏈 | 強制調用者處理(受檢異常) |
| 配合關係 | 方法內 throw 後建議聲明 throws | 配合 try-catch 實現傳播 |
try {
// 風險代碼
} catch (ExceptionType e) {
// 處理邏輯:記錄日誌、恢復、轉換異常、拋出
} finally {
// 清理資源:關流、釋放鎖、關連接
}
Throwable 子類,多 catch 子類在前父類在後e.printStackTrace() 或日誌記錄,嚴禁空 catch// 1. 定義
class DivideByZeroException extends Exception {
public DivideByZeroException(String msg) { super(msg); }
}
// 2. 業務拋出
class Calculator {
public int div(int a, int b) throws DivideByZeroException {
if (b == 0) throw new DivideByZeroException("除數不能為零");
return a / b;
}
}
// 3. 調用處理
public static void main(String[] args) {
try { System.out.println(new Calculator().div(10, 0)); }
catch (DivideByZeroException e) {
System.err.println(e.getMessage()); // 除數不能為零
e.printStackTrace();
}
}
throw 是動作,throws 是契約。異常機制將錯誤處理從業務邏輯中分離,構建「正常流」與「異常流」雙軌制。掌握兩者協作,配合 finally 資源管理、自定義異常語義化,是寫出專業級 Java 代碼的必修課。