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

Java 異常處理體系完整指南:從 Throwable 到自定義異常與 finally 最佳實踐

     編輯:Java 綜合教學

Java 異常處理體系完整指南:從 Throwable 到自定義異常與 finally 最佳實踐

異常處理是 Java 程序健壯性的基石。本文系統梳理異常體系結構、核心 API、捕獲與拋出機制、自定義異常設計、`finally` 語義、以及實戰最佳實踐,助你寫出可維護、可診斷的異常處理代碼。

一、異常體系結構總覽

java.lang.Object
└── java.lang.Throwable (所有異常/錯誤的父類)
    ├── java.lang.Error (嚴重錯誤,程序無法恢復,不應捕獲)
    │   ├── OutOfMemoryError
    │   ├── StackOverflowError
    │   ├── NoClassDefFoundError
    │   └── ...
    └── java.lang.Exception (異常,程序可捕獲處理)
        ├── java.lang.RuntimeException (運行時異常,非受檢)
        │   ├── NullPointerException
        │   ├── IllegalArgumentException
        │   ├── IndexOutOfBoundsException
        │   ├── ArithmeticException
        │   └── ...
        └── 受檢異常 (Checked Exception, 編譯強制處理)
            ├── IOException
            ├── SQLException
            ├── ClassNotFoundException
            ├── InterruptedException
            └── ...

核心區別

維度ErrorRuntimeException (非受檢)受檢異常 (Checked)
原因JVM/硬件/系統級故障邏輯錯誤/編程疏忽外部環境/資源不可控
可恢復性不可恢復可通過代碼修正避免可預期並編碼處理
編譯檢查無無強制 try-catch 或 throws
典型處理記錄日誌、優雅退出修復 Bug、參數校驗try-catch 處理/向上拋出
繼承關係繼承 Error繼承 RuntimeException繼承 Exception (非 RuntimeException)

二、Throwable 核心方法

Throwable t = new Exception("詳細信息");

// 1. 獲取簡短描述:類全名 + ": " + message
String toString = t.toString(); // "java.lang.Exception: 詳細信息"

// 2. 獲取詳細信息 (構造器傳入的 message)
String msg = t.getMessage(); // "詳細信息"

// 3. 打印堆棧追蹤到標準錯誤流
t.printStackTrace(); 
// 輸出格式:
// java.lang.Exception: 詳細信息
//     at com.example.Main.method(Main.java:10)
//     at com.example.Main.main(Main.java:5)

// 4. 獲取堆棧追蹤元素數組 (程序化分析)
StackTraceElement[] elements = t.getStackTrace();

// 5. 獲取原因鏈 (鏈式異常)
Throwable cause = t.getCause();
t.initCause(cause); // 設置原因

三、捕獲異常:try-catch-finally 完整語法

try {
    // 高風險代碼
    int result = 10 / 0;
    String s = null; s.length();
} catch (ArithmeticException | NullPointerException e) { // Java 7+ 多捕獲
    // 處理特定異常
    System.err.println("捕獲到: " + e.getClass().getSimpleName());
    e.printStackTrace();
} catch (Exception e) {
    // 捕獲父類異常 (放在最後)
    System.err.println("其他異常: " + e.getMessage());
} finally {
    // 無論是否異常、是否 return/throw,均執行 (除非 System.exit)
    System.out.println("finally 執行: 資源清理、鎖釋放、關閉連接");
}

關鍵規則

  1. 捕獲順序:子類在前,父類在後。若 `catch (Exception e)` 在前,後面的 `catch (NullPointerException e)` 永不可達,編譯錯誤。
  2. finally 必執行:即使 `try`/`catch` 中有 `return`、`throw`、`break`、`continue`,`finally` 仍會執行。例外:`System.exit(0)`、JVM 崩潰、無限循環/死鎖導致線程不終止。
  3. try-with-resources (Java 7+):自動關閉實現 `AutoCloseable` 的資源,優於手動 `finally` 關閉。
    try (InputStream in = new FileInputStream("a.txt");
                 OutputStream out = new FileOutputStream("b.txt")) {
                // 使用資源
            } catch (IOException e) {
                // 處理異常
            } // 編譯器自動生成 finally 關閉邏輯,且異常抑制機制完善

四、拋出異常:throw vs throws

關鍵字位置作用後接
throw方法體內部拋出一個異常對象實例`new ExceptionType("msg")`
throws方法聲明上聲明方法可能拋出的異常類型異常類列表,逗號分隔
public void readFile(String path) throws IOException, ParseException { // 聲明
    if (path == null) {
        throw new IllegalArgumentException("路徑不能為空"); // 拋出非受檢,無需聲明
    }
    try (FileInputStream fis = new FileInputStream(path)) {
        // ...
    } catch (FileNotFoundException e) {
        // 包裝為業務異常拋出
        throw new BusinessException("FILE_NOT_FOUND", "文件不存在: " + path, e);
    }
}

拋出異常決策樹

  • 能在當前方法有意義處理 (如重試、降級、記錄) → `try-catch` 處理,不向上拋出。
  • 無法處理,需調用者決策 → `throws` 聲明 (受檢) 或拋出 `RuntimeException` 子類 (非受檢)。
  • 跨層傳遞時:包裝異常 (保留原始 `cause`),轉換為當前層級語義的異常 (如 DAO 層 `SQLException` → Service 層 `DataAccessException`)。

五、自定義異常設計規範

1. 異常類模板

// 非受檢業務異常 (推薦繼承 RuntimeException,減少 throws 污染)
public class BusinessException extends RuntimeException {
    private final int errorCode;      // 業務錯誤碼
    private final String errorMsg;    // 用戶可讀訊息
    private final Object[] args;      // 訊息佔位符參數

    public BusinessException(int errorCode, String errorMsg, Object... args) {
        super(errorMsg);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
        this.args = args;
    }
    // Getters...
}

// 受檢異常 (需強制調用者處理的場景,如檢查型業務規則)
public class ValidationException extends Exception {
    private final List errors;
    public ValidationException(List errors) {
        super("參數校驗失敗");
        this.errors = errors;
    }
}

2. 設計原則

  • 命名以 Exception 結尾:`UserNotFoundException`、`InsufficientBalanceException`。
  • 提供錯誤碼:便於前端國際化、錯誤碼映射、監控告警。
  • 保留堆棧追蹤:構造器調用 `super(message, cause)`,切勿 `new Exception(e.getMessage())` 丟失堆棧。
  • 不可變:錯誤碼、訊息字段 `final`。
  • 文檔化:`@throws` 標註拋出條件。

六、finally 深度解析與最佳實踐

1. 執行時機保證

public int testFinally() {
    try {
        return 1; // 1. 計算返回值 1,暫存
    } finally {
        System.out.println("finally"); // 2. 執行 finally
        // return 2; // ⚠️ 若這裡 return,會覆蓋 try 的返回值!極度不推薦
    }
    // 3. 返回暫存值 1
}

2. 資源管理黃金法則

// ❌ 手動 finally 關閉 (繁瑣、易漏、異常抑制問題)
InputStream in = null;
try {
    in = new FileInputStream("a.txt");
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (in != null) try { in.close(); } catch (IOException ignored) {}
}

// ✅ try-with-resources (Java 7+)
try (InputStream in = new FileInputStream("a.txt")) {
    // 使用
} catch (IOException e) {
    // 處理
}
// 編譯器生成:
// 1. 正常/異常退出時調用 close()
// 2. 若 try 塊拋異常且 close() 也拋異常,close() 的異常被「抑制」附加到主異常 (getSuppressed())

3. finally 中避免 return/throw

會導致異常丟失或返回值覆蓋,極難排查。靜態分析工具 (SpotBugs, SonarQube) 會報警。

七、實戰最佳實踐清單

  1. 異常分層:DAO 層拋 `DataAccessException`,Service 層拋 `BusinessException`,Controller 層統一處理轉 HTTP 狀態碼。
  2. 統一異常處理器:`@RestControllerAdvice` + `@ExceptionHandler` 返回標準錯誤響應體 (錯誤碼、訊息、請求 ID、時間戳)。
  3. 日誌記錄原則:
    • 業務異常 (預期內) → `WARN` 級別,不打印堆棧 (如 `UserNotFoundException`)。
    • 系統異常 (預期外) → `ERROR` 級別,**必須打印完整堆棧** (`log.error("msg", e)`)。
  4. 避免吞沒異常:`catch (Exception e) {}` 絕對禁止。至少 `log.error` 或拋出包裝異常。
  5. 參數校驗前置:Controller 入口用 `@Valid` + `BindingResult`,Service 入口用 `Assert`/`Preconditions` 拋 `IllegalArgumentException`。
  6. 異常包裝保留原因:`throw new BizException("msg", e)`,而非 `throw new BizException(e.getMessage())`。
  7. 非受檢 vs 受檢選型:默認非受檢 (RuntimeException),僅當調用者必須顯式處理 (如資源鎖釋放、補償事務) 時用受檢。
  8. 全局錯誤碼體系:如 `1xxxx` 參數錯誤、`2xxxx` 業務邏輯錯誤、`3xxxx` 第三方依賴錯誤、`5xxxx` 系統錯誤。
  9. 測試異常場景:單元測試覆蓋 `throw` 路徑、邊界條件、資源洩漏檢查。

八、總結

Java 異常處理的核心在於清晰的責任劃分:誰拋出、誰捕獲、誰處理、誰記錄。遵循「具體異常具體捕獲、資源用 try-with-resources、業務異常非受檢並帶錯誤碼、統一攔截轉換為友好響應」原則,即可構建健壯、可觀測、可維護的異常處理體系。

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