首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 異常處理核心:throw 關鍵字拋出異常的完整用法與最佳實踐

     編輯:Java 相關知識

前言

throw 是 Java 主動拋出異常的唯一關鍵字,配合 throws 聲明、try-catch 捕獲、自定義異常體系,構成了 Java 健壯錯誤處理的骨架。本文從語法細節、異常鏈保護、業務異常設計、效能考量四個維度,系統講解 throw 的正確打開方式。

一、基礎語法與執行流程

1.1 直接拋出異常實例

public void validateAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("年齡不能為負數:" + age);
    }
    if (age > 150) {
        throw new IllegalArgumentException("年齡超過合理範圍:" + age);
    }
}
  • throw 後必須跟 Throwable 子類實例(new XxxException(...))。
  • 拋出即刻終止當前方法執行,控制權轉給調用棧最近的匹配 catch 或向上傳播。

1.2 捕獲後重新拋出(保留堆疊軌跡)

public void readConfig(File file) throws ConfigException {
    try (FileInputStream fis = new FileInputStream(file)) {
        // 解析邏輯
    } catch (IOException e) {
        // ❌ 錯誤:new Exception(e) 會丟失原始堆疊
        // ✅ 正確:直接 throw e,或包裝保留 cause
        throw new ConfigException("讀取配置失敗:" + file.getAbsolutePath(), e);
    }
}

鐵律:永遠保留 cause,否則生產排查無從下手。

二、受檢異常 vs 非受檢異常:選型決策

維度受檢異常 (extends Exception)非受檢異常 (extends RuntimeException)
編譯強制必須 throws 或 catch可選
適用場景可預期、可恢復的業務狀態(檔案不存在、資料庫死鎖、第三方服務降級)程式錯誤(參數非法、空指針、狀態非法)、不可恢復系統故障
API 契約方法簽名顯式聲明,調用者**知情必處理**隱式傳播,調用者**可選擇性處理**
濫用後果層層 throws 污染介面、try-catch 濫用掩蓋真錯關鍵錯誤被吞沒、無感知崩潰

2.1 經驗法則

  1. 業務邏輯可預期失敗 → 自定義 BizException extends Exception(受檢)。
  2. 程式 Bug / 前置條件違反 → IllegalArgumentException、IllegalStateException 等標準非受檢異常。
  3. 底層受檢異常向上轉換:catch (SQLException e) { throw new DataAccessException("查詢失敗", e); } —— 統一轉為業務受檢異常,屏蔽實現細節。

三、自定義異常體系設計規範

3.1 分層結構範例

// 基礎業務異常(受檢)
public class BizException extends Exception {
    private final String errorCode;
    public BizException(String errorCode, String message) { super(message); this.errorCode = errorCode; }
    public BizException(String errorCode, String message, Throwable cause) { super(message, cause); this.errorCode = errorCode; }
    public String getErrorCode() { return errorCode; }
}

// 具體業務異常
public class UserNotFoundException extends BizException {
    public UserNotFoundException(String username) {
        super("USER_NOT_FOUND", "用戶不存在:" + username);
    }
}

public class InsufficientBalanceException extends BizException {
    public InsufficientBalanceException(long accountId, BigDecimal required, BigDecimal actual) {
        super("INSUFFICIENT_BALANCE", String.format("賬戶 %d 餘額不足:需 %s,實 %s", accountId, required, actual));
    }
}

// 系統級非受檢異常
public class SystemUnavailableException extends RuntimeException {
    public SystemUnavailableException(String system, Throwable cause) {
        super("下游系統 " + system + " 不可用", cause);
    }
}

3.2 錯誤碼規範

  • 格式:MODULE_BIZ_ERROR,如 ORDER_PAY_TIMEOUT、USER_PHONE_DUPLICATE。
  • 前端/網關按錯誤碼國際化、熔斷、重試,而非解析訊息文本。

四、異常處理進階模式

4.1 統一異常處理器

@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BizException.class)
    public R handleBiz(BizException e) {
        log.warn("業務異常:{} - {}", e.getErrorCode(), e.getMessage());
        return R.fail(e.getErrorCode(), e.getMessage());
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public R handleValidation(ConstraintViolationException e) {
        String msg = e.getConstraintViolations().stream()
                .map(v -> v.getPropertyPath() + " " + v.getMessage())
                .collect(Collectors.joining("; "));
        return R.fail("PARAM_INVALID", msg);
    }

    @ExceptionHandler(Exception.class)
    public R handleUnknown(Exception e) {
        log.error("系統未知異常", e); // 完整堆疊入日誌
        return R.fail("SYSTEM_ERROR", "系統繁忙,請稍後重試");
    }
}

4.2 斷言式驗證工具類

public final class Asserts {
    public static void isTrue(boolean expr, String errorCode, String msg) {
        if (!expr) throw new BizException(errorCode, msg);
    }
    public static void notNull(Object obj, String errorCode, String msg) {
        if (obj == null) throw new BizException(errorCode, msg);
    }
    public static void notEmpty(Collection coll, String errorCode, String msg) {
        if (coll == null || coll.isEmpty()) throw new BizException(errorCode, msg);
    }
}

// 使用
Asserts.notNull(user, "USER_NOT_FOUND", "用戶不存在");
Asserts.isTrue(balance.compareTo(amount) >= 0, "INSUFFICIENT_BALANCE", "餘額不足");

4.3 異常轉換鏈:底層 → 業務 → HTTP 狀態碼

SQLException (受檢)
    │
    ▼ catch → throw new DataAccessException("查詢失敗", e)  // 受檢業務異常
    │
    ▼ ControllerAdvice 捕獲 BizException 子類
    │
    ▼ 映射表:
        USER_NOT_FOUND          → 404 Not Found
        INSUFFICIENT_BALANCE    → 409 Conflict
        DATA_ACCESS_ERROR       → 500 Internal Server Error (並告警)
        SYSTEM_UNAVAILABLE      → 503 Service Unavailable (並熔斷)

五、效能與避坑指南

  1. 避免在循環/高頻路徑 new Exception:堆疊採樣開銷大。熱路徑用 return Result.fail(...) 而非拋異常控制流。
  2. 不要用異常做業務流程控制:if (user == null) throw ... 可改 Optional / Result 模式。
  3. 序列化場景實現 serialVersionUID:跨 JVM 傳遞異常(RMI、分布式隊列)必須,否則反序列化失敗。
  4. 日誌記錄完整堆疊:log.error("msg", e) 而非 log.error(e.getMessage())。

六、總結:throw 使用檢查清單

檢查項標準
異常類型選擇可恢復業務 → 受檢;Bug/不可恢復 → 非受檢
錯誤碼有、唯一、可國際化、前端可據此決策
Cause 保留所有包裝/轉換必須 super(msg, cause)
堆疊完整性不丟失、不重複填充、日誌完整記錄
熱路徑無異常高頻調用路徑無 new Exception、無控制流異常
序列化就緒跨進程異常類有 serialVersionUID

把這份清單貼在顯示器邊,每次 throw new XxxException 時心裡過一遍——好異常設計,是系統可觀測、可運維、可演進的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved