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

Effective Java 異常處理精華:項目 57-65 核心原則與實戰模式

     編輯:Java 程式設計入門

項目 57:只在異常情況使用異常

反模式:用異常控制流程


// 糟糕:用 ArrayIndexOutOfBoundsException 結束迴圈
try {
    int i = 0;
    while (true) range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
    // 視為正常結束,什麼都不做
}

正統寫法:標準迴圈


for (Mountain m : range) m.climb();

危害分析

  • 可讀性:意圖模糊,違背異常語義
  • 效能:try-catch 阻礙 JIT 優化,異常建立堆疊追蹤開銷極大
  • 掩蓋 Bug:若 climb() 內部也拋出相同異常,會被外層捕獲吞噬,導致真實錯誤隱形

API 設計啟示

提供狀態檢查方法 (hasNext()、hasMoreElements()) 讓客戶端避免異常控制流:


// Iterator 設計:狀態檢查 + 取值分離
for (Iterator i = c.iterator(); i.hasNext(); ) {
    Foo f = i.next(); // 無異常風險
}

項目 58:可恢復用受檢異常,程式錯誤用執行時異常

類別適用場景範例處理義務
受檢異常呼叫端可合理恢復餘額不足、檔案不存在、網路超時強制 catch 或 throws 傳播
執行時異常程式邏輯錯誤 (Bug)空指標、陣列越界、非法參數無義務捕獲,修正代碼而非處理
ErrorJVM/資源嚴重故障OOM、StackOverflow、類別載入失敗通常不捕獲,讓程式崩潰

實用技巧:受檢異常攜帶恢復資訊


public class InsufficientFundsException extends Exception {
    private final double balance;
    private final double required;
    public InsufficientFundsException(double balance, double required) {
        super(String.format("餘額 %.2f 不足支付 %.2f", balance, required));
        this.balance = balance; this.required = required;
    }
    public double getBalance() { return balance; }
    public double getShortfall() { return required - balance; }
}

呼叫端可捕獲後讀取具體數值決定重試/提示/轉帳等恢復策略。

項目 59:避免不必要的受檢異常

判斷標準:兩個條件皆滿足才用受檢異常

  1. 呼叫端無法透過 API 正確使用預防該異常 (如檢查檔案是否存在)
  2. 異常發生時,呼叫端能採取有意義的恢復行動 (而非僅 log/exit)

濫用受檢異常的跡象


// 僅 log 或 exit,無實質恢復
try { doSomething(); }
catch (TheCheckedException e) {
    e.printStackTrace(); System.exit(1);
}

// 或直接轉為 AssertionError
try { doSomething(); }
catch (TheCheckedException e) { throw new AssertionError(); }

此類情況改用 未受檢異常 更誠實、更簡潔。

項目 60:優先使用標準異常

標準異常適用場景
IllegalArgumentException參數值不合法 (非 null)
IllegalStateException物件狀態不適合該操作 (如未初始化就調用)
NullPointerException禁止 null 卻傳入 null
IndexOutOfBoundsException索引參數越界
ConcurrentModificationException禁止並發修改時檢測到修改
UnsupportedOperationException物件不支援該操作 (如唯讀集合 add)

優勢:復用代碼、API 易學、可讀性高、工具鏈支援完善。

項目 61:拋出與抽象層級對應的異常 (異常轉譯)

問題:底層實現細節洩漏至高層 API


// 糟糕:SQLException 直接拋出至 Service 層
public User findById(long id) throws SQLException { ... }

解決:異常鏈保留根因


try {
    return jdbcTemplate.queryForObject(sql, mapper, id);
} catch (DataAccessException e) {
    throw new UserNotFoundException("用戶不存在: " + id, e); // 根因包裝
}
  • 高層異常語義清晰 (UserNotFoundException)
  • getCause() 保留完整堆疊追蹤,除錯無損
  • 實現細節 (JDBC/JPA) 完全隔離

項目 63:異常攜帶失敗捕獲資訊

拋出異常時封裝關鍵上下文:


throw new IndexOutOfBoundsException(
    String.format("索引 %d 超出範圍 [0, %d)", index, size)
);
// 或自訂異常提供 getter
throw new InsufficientFundsException(balance, amount);

堆疊追蹤 + 詳細訊息 = 秒級定位問題。

項目 64:努力實現失敗原子性

失敗後物件保持可用狀態 (調用前狀態):

  1. 不可變物件:天然原子性,失敗只影響新物件建立
  2. 參數前置驗證:修改狀態前先檢查
    
    public Object pop() {
        if (size == 0) throw new EmptyStackException(); // 狀態未變
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
    
  3. 副本恢復:操作前備份,失敗時還原 (開銷大,慎用)

注意:併發修改下難以保證,需鎖/不可變/無鎖資料結構配合。

項目 65:不要忽略異常


// 糟糕:空 catch 吞噬異常
try { dosomething(); } catch (SomeException e) { }

// 可接受:關閉資源時忽略 (資料已讀完)
try { inputStream.close(); } catch (IOException e) { log.debug("關閉流失敗", e); }

// 原則:至少記錄日誌,否則 Bug 會在未來爆發

總結:異常處理黃金法則

  1. 異常只用於異常情況,非控制流工具
  2. 可恢復 → 受檢異常;Bug → 執行時異常
  3. 優先復用標準異常,語義精準
  4. 異常轉譯 + 異常鏈,隔離層級細節
  5. 異常攜帶完整上下文資訊
  6. 追求失敗原子性,保護物件不變式
  7. 絕不空 catch,至少記錄日誌
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved