核心區別一覽
| 維度 | throw | throws |
| 位置 | 方法體內部 | 方法簽名聲明處 |
| 作用 | 拋出具體異常對象實例 | 聲明方法可能拋出的異常類型 |
| 語法 | throw new ExceptionType(); | throws ExceptionType1, ExceptionType2 |
| 執行時機 | 運行時動態拋出 | 編譯時靜態檢查 |
| 必須處理 | 受檢異常需捕獲或繼續拋出 | 調用者必須捕獲或繼續聲明 |
throw 詳細用法
public void validateAge(int age) {
// 1. 拋出運行時異常 (無需聲明)
if (age < 0) {
throw new IllegalArgumentException("年齡不能為負數: " + age);
}
// 2. 拋出受檢異常 (方法必須聲明 throws)
if (age > 150) {
throw new InvalidAgeException("年齡超出合理範圍: " + age);
}
// 3. 捕獲後重新拋出 (保留堆棧)
try {
riskyOperation();
} catch (IOException e) {
throw new BusinessException("操作失敗", e); // 包裝異常鏈
}
// 4. 直接重拋捕獲的異常
try {
anotherRisky();
} catch (SQLException e) {
throw e; // 原樣拋出,堆棧保持不變
}
}
throws 詳細用法
// 1. 聲明單一受檢異常
public void readFile(String path) throws IOException {
Files.readAllLines(Paths.get(path));
}
// 2. 聲明多個異常
public void processData() throws IOException, SQLException, TimeoutException {
// ...
}
// 3. 父類異常覆蓋子類 (不推薦,語義不清)
public void vague() throws Exception { // 太寬泛
// ...
}
// 4. 覆寫方法的異常聲明規則
// 子類拋出的異常必須是父類聲明異常的子類或相同
class Parent {
void method() throws IOException {}
}
class Child extends Parent {
@Override
void method() throws FileNotFoundException {} // ✓ 子類異常
// void method() throws Exception {} // ✗ 父類未聲明
}
throw 與 throws 協作流程
// 服務層
public class UserService {
public void register(User user) throws ValidationException, DuplicateUserException {
// 業務校驗 - 主動拋出
if (user.getName() == null) {
throw new ValidationException("用戶名不能為空");
}
// 數據庫操作 - 可能拋出 SQLException
try {
userDao.insert(user);
} catch (SQLException e) {
if (e.getErrorCode() == 1062) { // MySQL 唯一鍵衝突
throw new DuplicateUserException("用戶已存在", e); // 包裝拋出
}
throw new SystemException("數據庫錯誤", e); // 其他包裝
}
}
}
// 控制器層 - 捕獲處理
@RestController
public class UserController {
@PostMapping("/register")
public Result register(@RequestBody User user) {
try {
userService.register(user);
return Result.ok();
} catch (ValidationException e) {
return Result.badRequest(e.getMessage());
} catch (DuplicateUserException e) {
return Result.conflict(e.getMessage());
} catch (SystemException e) {
log.error("註冊系統異常", e);
return Result.error("系統繁忙");
}
}
}
三種異常拋出方式對比
| 方式 | 代碼示例 | 特點 | 適用場景 |
| JVM 自動拋出 | int a = 1/0; // ArithmeticException | 運行時錯誤、Bug | 編程錯誤、不可預期 |
| throw 手動拋出 | throw new BizException("業務邏輯錯誤"); | 業務規則違反、參數校驗失敗 | 可預期的業務異常 |
| throws 聲明拋出 | void method() throws IOException {} | 方法無法處理、需調用者決策 | 受檢異常傳播、API 契約 |
異常鏈與原因保留 (關鍵最佳實踐)
// ✅ 正確:構造器傳入 cause,保留完整堆棧鏈
throw new BusinessException("訂單創建失敗", originalException);
// ❌ 錯誤:僅設置消息,丟失原始堆棧
throw new BusinessException("訂單創建失敗: " + originalException.getMessage());
// ✅ 正確:捕獲後包裝重拋
try {
jdbcTemplate.update(sql);
} catch (DataAccessException e) {
throw new PersistenceException("數據庫操作失敗", e);
}
// 異常鏈遍歷
Throwable cause = e;
while (cause != null) {
System.out.println(cause.getClass().getName() + ": " + cause.getMessage());
cause = cause.getCause();
}
編程規範建議
- 業務異常繼承 RuntimeException:非受檢,不強制捕獲,由全局異常處理器統一處理
- 受檢異常僅用於「調用者可恢復」場景:IO、網絡、數據庫等資源操作
- 方法簽名聲明具體異常:避免
throws Exception,語義不明
- 捕獲異常必須處理:記錄日誌、轉換拋出、恢復邏輯,嚴禁空 catch
- 異常包裝保留 cause:構造器傳入原始異常,保証診斷能力
- finally 不控制流程:不 return/throw,只做清理
- 自定義異常實現 Serializable:支持分布式傳輸
異常層級架構設計
Exception (受檢)
├── SystemException // 系統級:數據庫、網絡、配置
│ ├── PersistenceException
│ ├── RemoteServiceException
│ └── ConfigurationException
└── ValidationException // 參數校驗 (可受檢也可非受檢)
RuntimeException (非受檢)
├── BusinessException // 業務邏輯:庫存不足、狀態錯誤
│ ├── InsufficientStockException
│ ├── InvalidStateException
│ └── DuplicateResourceException
├── SecurityException // 認證授權
└── IllegalArgumentException // 參數非法 (JDK 內置)
清晰區分 throw (動作) 與 throws (聲明),構建分層異常體系,保留完整因果鏈,是構建可維護、可診斷 Java 系統的基石。