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

Java 異常與錯誤處理深度解析:從架構到最佳實踐

     編輯:Java 相關知識

核心概念區分

特性Exception (異常)Error (錯誤)
繼承自java.lang.Exceptionjava.lang.Error
性質程式邏輯可預期、可處理的異常狀況JVM/系統級嚴重問題,通常無法恢復
典型例子NullPointerException, IOException, SQLExceptionOutOfMemoryError, StackOverflowError, NoClassDefFoundError
處理責任開發者**必須**捕獲或拋出非應用層責任,通常只能記錄並終止
檢查機制受檢異常編譯強制處理非受檢,編譯器不強制

兩者共同繼承自 Throwable,只有 Throwable 及其子類可被 throw/catch。

異常分類體系

Throwable
├── Error (系統錯誤,不處理)
└── Exception
    ├── RuntimeException (非受檢異常 Unchecked)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   ├── IllegalArgumentException
    │   └── ClassCastException
    └── 其他 (受檢異常 Checked)
        ├── IOException
        ├── SQLException
        ├── ClassNotFoundException
        └── InterruptedException
  • 受檢異常:繼承 Exception 但非 RuntimeException。編譯器強制 try-catch 或 throws,適用「呼叫者**合理預期能恢復**」的場景 (如檔案不存在、網路中斷)。
  • 非受檢異常:繼承 RuntimeException。通常為**程式邏輯錯誤** (空指標、越界、參數非法),應透過**防禦性程式設計**預防,而非捕獲處理。

異常處理語法全解

1. 基本 try-catch-finally

try {
    // 高風險代碼
    FileInputStream fis = new FileInputStream("data.txt");
} catch (FileNotFoundException e) {
    // 處理特定異常
    logger.error("檔案不存在", e);
} catch (IOException e) {
    // 處理父類異常 (須放在子類之後)
    logger.error("IO 錯誤", e);
} finally {
    // 無論是否異常都會執行 (除非 System.exit)
    // 釋放資源、關閉連線
}

2. try-with-resources (Java 7+ 強烈推薦)

// 實作 AutoCloseable 的資源會自動關閉,順序相反
try (FileInputStream fis = new FileInputStream("data.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line;
    while ((line = br.readLine()) != null) {
        process(line);
    }
} catch (IOException e) {
    logger.error("讀取失敗", e);
}
// 無需 finally 手動 close,編譯器自動生成隱含 finally

3. 多重捕獲

catch (FileNotFoundException | SocketException e) {
    // Java 7+ 同一塊處理多種無繼承關係的異常
    logger.error("資源不可用", e);
}

進階主題

異常鏈與資訊保留

拋出新異常時,**務必保留原始原因**,否則除極困難:

try {
    jdbcTemplate.update(sql);
} catch (DataAccessException e) {
    // 錯誤:原始堆疊丟失
    // throw new BusinessException("更新失敗");

    // 正確:建立異常鏈
    throw new BusinessException("更新失敗", e); // 建構子傳入 cause

    // 或手動設定
    BusinessException be = new BusinessException("更新失敗");
    be.initCause(e);
    throw be;
}

列印堆疊時會顯示 Caused by: ... 完整鏈路。

自訂異常設計原則

  1. 業務異常繼承 RuntimeException (非受檢),配合全域異常處理器統一轉碼碼/訊息
  2. 受檢異常僅用於「呼叫者**必須**處理」的資源操作 (IO、SQL)
  3. 異常類提供 code、data 欄位,支援國際化訊息
  4. 命名以 Exception 結尾,如 InsufficientBalanceException

覆寫方法的異常規則

  • 子類覆寫方法**拋出的受檢異常範圍不能大於父類** (可拋出子類、較少、或不拋出)
  • 非受檢異常無限制
  • 遵循里氏替換原則:呼叫者按父類型調用時,預期的異常契約不應被打破

實戰反模式與正確做法

反模式問題正確做法
catch (Exception e) {} 吞沒異常錯誤無聲無息,難以排查至少 log.error,或拋出自訂異常
throw new Exception(e.getMessage())丟失堆疊追蹤throw new MyException("msg", e)
用異常控制流程效能差、語意不清用條件判斷、Optional、Result 物件
finally 裡拋出異常掩蓋 try 區塊的原始異常finally 只做清理,不拋出異常
捕獲後直接 e.printStackTrace()輸出到 stdout,無法整合日誌系統用 SLF4J/Log4j2 log.error("msg", e)

資源清理最佳實踐

// ✅ 推薦:try-with-resources
public void readConfig() {
    try (InputStream is = Files.newInputStream(Paths.get("config.yaml"))) {
        parse(is);
    } catch (IOException e) {
        throw new ConfigException("讀取配置失敗", e);
    }
}

// ❌ 避免:手動 close 易漏
public void readConfigOld() {
    InputStream is = null;
    try {
        is = Files.newInputStream(Paths.get("config.yaml"));
        parse(is);
    } catch (IOException e) {
        throw new ConfigException("讀取配置失敗", e);
    } finally {
        if (is != null) {
            try { is.close(); } catch (IOException ignored) {}
        }
    }
}

全域異常處理 (Spring Boot 範例)

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public R<Void> handleBiz(BusinessException e) {
        return R.fail(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public R<Void> handleOther(Exception e) {
        log.error("系統異常", e);
        return R.fail(500, "系統內部錯誤");
    }
}

總結

  1. **區分 Exception/Error**:前者處理,後者記錄並終止
  2. **受檢 vs 非受檢**:前者表達「必須處理」,後者表達「程式錯誤」
  3. **永遠保留異常鏈**:建構子傳 cause 或 initCause()
  4. **優先使用 try-with-resources**:自動資源管理,杜絕洩漏
  5. **不吞沒、不濫用、不控制流程**:異常是為了「異常狀況」,而非正常邏輯分支
  6. **分層處理**:DAO 拋受檢/包裝異常 → Service 轉業務異常 → Controller/Advice 統一響應

掌握這些原則,能寫出**健壯、可維護、易除錯**的 Java 系統。

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