Java 異常機制中,throws(聲明拋出)與 throw(拋出實例)是方法簽名與方法體內的分工。搞清二者差異、檢查型異常規則、自定義異常最佳實踐,是寫出健壯代碼的基石。
| 關鍵字 | 位置 | 作用 | 後跟內容 |
|---|---|---|---|
throws | 方法聲明 | 聲明方法可能拋出的異常類型,轉嫁處理責任給調用者 | 異常類列表(用逗號分隔) |
throw | 方法體內 | 實際拋出一個異常對象實例,中斷當前執行流程 | new ExceptionType() 實例 |
修飾符 返回類型 方法名(參數列表) throws ExceptionType1, ExceptionType2 { ... }
Exception 但非 RuntimeException):必須用 throws 聲明或 try-catch 捕獲,否則編譯報錯。Error、RuntimeException 及子類):可選聲明,編譯器不強制,運行時由 JVM 自動拋出。import java.io.*;
public class ThrowsDemo {
// 聲明拋出受檢異常
static void readFile(String path) throws FileNotFoundException, IOException {
FileInputStream fis = new FileInputStream(path); // 可能拋 FileNotFoundException
int b;
while ((b = fis.read()) != -1) { // 可能拋 IOException
System.out.print((char) b);
}
fis.close();
}
public static void main(String[] args) {
try {
readFile("data.txt"); // 調用者必須處理
} catch (FileNotFoundException e) {
System.err.println("檔案不存在:" + e.getMessage());
} catch (IOException e) {
System.err.println("讀取失敗:" + e.getMessage());
}
}
}
throw new ExceptionType("錯誤描述");
Throwable 或其子類實例。throw 後的代碼不可達,編譯器會報錯。
// 自定義業務異常(繼承 RuntimeException 為非受檢,或 Exception 為受檢)
class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String msg) { super(msg); this.code = code; }
public int getCode() { return code; }
}
public class ThrowDemo {
static int divide(int a, int b) {
if (b == 0) {
// 主動拋出業務異常
throw new BusinessException(400, "除數不能為零");
}
if (b < 0) {
// 拋出受檢異常,需在方法聲明 throws
throw new IllegalArgumentException("除數不能為負數"); // 非受檢,可不聲明
}
return a / b;
}
public static void main(String[] args) {
try {
int r = divide(10, 0);
} catch (BusinessException e) {
System.out.println("業務錯誤 [" + e.getCode() + "]:" + e.getMessage());
}
}
}
// 底層方法:捕獲 IO 異常,轉為業務異常拋出
void saveUser(User u) throws BusinessException {
try {
db.insert(u);
} catch (SQLException e) {
// 記錄完整堆疊,拋出業務語義異常
throw new BusinessException(500, "用戶保存失敗", e); // 支持 cause 鏈
}
}
// 上層調用:繼續 throws 或 catch 處理
void register(User u) throws BusinessException {
saveUser(u); // 透傳
}
void process(File file) throws AppException {
try (InputStream in = Files.newInputStream(file.toPath())) {
// 處理流
} catch (IOException e) {
// 若 try 塊也拋異常,close() 拋出的異常會被 suppressed
throw new AppException("處理失敗", e);
}
}
Exception (受檢:需聲明/捕獲)
├─ BusinessException (業務邏輯錯誤:參數非法、狀態不符)
├─ ResourceException (資源不可用:DB斷連、第三方超時)
└─ SystemException (系統級錯誤:配置缺失、環境異常)
RuntimeException (非受檢:編程錯誤、不可恢復)
├─ IllegalArgumentException
├─ IllegalStateException
└─ NullPointerException
catch (Exception e) {} 禁止;至少記錄日誌或轉換拋出。throw new XxxException("msg", cause)。@throws 條件與含義。| 誤區 | 正確做法 |
|---|---|
| 所有方法都 throws Exception | 精確聲明具體異常類型,便於調用者針對性處理 |
| 用異常控制業務流程 | 用條件判斷、Optional、Result 模式;異常僅用於「異常」情況 |
| catch 後只打印堆疊 | 記錄結構化日誌、轉換業務異常、或補償重試 |
| 自定義異常不攜帶上下文 | 攜帶錯誤碼、關鍵參數、原因鏈,支援快速定位 |
throws 是方法契約,聲明「我可能拋什麼」,轉嫁責任。throw 是執行動作,實際製造異常對象,中斷流程。掌握這兩把刷,你的代碼將在正確性、可讀性、可維護性上躍升一個台階。