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

《Practical Java》讀書筆記(一):equals、異常、finally 與構造器陷阱

     編輯:Java 相關知識

前言

《Practical Java》是 Java 進階必讀經典。本文整理前八章核心心得,涵蓋 equals() 契約、異常處理細節、finally 坑點、構造器拋異常模式等實戰關鍵點。

1. equals() 方法的正確姿勢

  • String 可直接用,JDK 已正確覆寫。
  • 大多數 JDK 類未覆寫 equals(),繼承自 Object(即 == 比較引用),慎用。
  • 自定義類覆寫三步驟:
    1. 引用相等性檢查(this == obj)
    2. 類型檢查(instanceof 或 getClass())
    3. 核心字段逐個比較
  • 鐵律:覆寫 equals() 必須覆寫 hashCode()。相等對象散列碼必須相等,否則 HashMap/HashSet 會失效(無法找到/重複存入)。
  • 繼承鏈:子類 equals() 中調用 super.equals() 復用父類比較邏輯。

2. 異常向上拋出時的「被覆蓋」陷阱

public void foo() throws Exception {
    try {
        throw new Exception("First Exception");  // 1 根因異常
    } catch (Exception e) {
        throw new Exception("Second Exception"); // 2 捕獲後拋新異常
    } finally {
        throw new Exception("Third Exception");  // 3 finally 再拋異常
    }
}

結果:調用者只能捕獲到 "Third Exception",根因 "First Exception" 和中間 "Second Exception" **完全丟失**,導致排查極難。

對策:

  • catch 中拋新異常時,用 throw new Exception("msg", originalCause); 保留鏈路(JDK 1.4+ 支持 getCause())。
  • finally 中絕不拋出異常,只做資源釋放;若釋放可能失敗,內部 try-catch 吞掉或記錄日誌。

3. throws 聲明:具體優於笼統

// 推薦:具體異常,調用者可針對性處理
void f() throws FileNotFoundException, EOFException, UnsupportedEncodingException

// 不推薦:父類異常,調用者只能 catch IOException,失去細粒度控制
void f() throws IOException

JDK 標準庫普遍採用具體聲明,這是良好 API 設計的體現。

4. 繼承/實現時的 throws 契約


interface A { void f() throws IOException; }
class B implements A {
    // 合法選擇:
    // 1. 不拋異常
    // 2. 拋 IOException
    // 3. 拋 IOException 子類(如 FileNotFoundException)
    // 4. 若有非 IOException 異常 → 必須內部捕獲處理,不得向上拋
    void f() { ... }
}

原因:面向接口編程,調用端按接口簽名處理異常。實現類若拋出更寬泛的檢查型異常,將破壞調用端的異常處理邏輯,編譯器會報錯攔截。

5. finally 塊的兩大雷區

雷區一:try 中 return,finally 修改返回值


int f() {
    try { return 1; }
    finally { return 2; }  // 直接覆蓋,調用者永遠拿到 2
}

雷區二:try 中 return,finally 拋異常


int f() {
    try { return 1; }
    finally { throw new RuntimeException(); } // 返回值丟失,異常向上拋
}

最佳實踐:

  • finally 僅做資源關閉/清理,不返回值、不拋異常。
  • 若必須在 finally 中操作可能失敗的資源,內部包 try-catch 並記錄日誌。

補充:唯一跳過 finally 的方式是 System.exit(0)(JVM 直接退出)。

6. 「別用異常控制流程」的界限

原則針對正常業務流程(如用異常代替 if-else 判斷用戶是否存在)。但異常流程(網絡斷開重連、文件讀取失敗重試)必須用異常處理,這是異常的設計初衷。

7. 構造器拋異常 vs 二段構造

為什麼構造器要拋異常?

  • 構造器無返回值,無法通過返回碼報錯。
  • 若構造失敗且調用者忽略異常,引用將指向 null,後續使用觸發 NullPointerException,錯誤現場遠離根因。

A a = null;
try { a = new A(); } catch (XXException e) { /* 記錄/處理 */ }
// 若這裡忽略異常繼續用 a → NPE

二段構造替代方案


class MyFileInputStream {
    String name;           // 無風險
    FileInputStream fis;   // 可能拋 FileNotFoundException

    MyFileInputStream(String name) { this.name = name; } // 第一段:安全初始化
    boolean init() {       // 第二段:風險操作
        try { fis = new FileInputStream(name); return true; }
        catch (FileNotFoundException e) { return false; }
    }
}
// 使用時必須檢查初始化標誌
if (stream.init()) { stream.read(); }

代價:每個方法入口需檢查標誌,增加複雜度。通常優先讓構造器拋異常,失敗快速暴露。

8. 捕獲異常後的責任:恢復有效狀態

捕獲異常且不再向上拋時,必須確保程序處於已知、可繼續運行的狀態:資源已釋放、數據一致、無副作用殘留。至少要記錄日誌;無法處理時誠實向上拋。

總結

《Practical Java》前章揭示的都是「寫得出但用不好會慘」的核心機制。掌握 equals/hashCode 契約、異常鏈保護、finally 純淨性、構造器失敗語義,能讓你的 Java 代碼從「能跑」進化為「健壯、可維護、可診斷」。

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