《Practical Java》是 Java 進階必讀經典。本文整理前八章核心心得,涵蓋 equals() 契約、異常處理細節、finally 坑點、構造器拋異常模式等實戰關鍵點。
Object(即 == 比較引用),慎用。this == obj)instanceof 或 getClass())equals() 必須覆寫 hashCode()。相等對象散列碼必須相等,否則 HashMap/HashSet 會失效(無法找到/重複存入)。equals() 中調用 super.equals() 復用父類比較邏輯。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 吞掉或記錄日誌。// 推薦:具體異常,調用者可針對性處理
void f() throws FileNotFoundException, EOFException, UnsupportedEncodingException
// 不推薦:父類異常,調用者只能 catch IOException,失去細粒度控制
void f() throws IOException
JDK 標準庫普遍採用具體聲明,這是良好 API 設計的體現。
interface A { void f() throws IOException; }
class B implements A {
// 合法選擇:
// 1. 不拋異常
// 2. 拋 IOException
// 3. 拋 IOException 子類(如 FileNotFoundException)
// 4. 若有非 IOException 異常 → 必須內部捕獲處理,不得向上拋
void f() { ... }
}
原因:面向接口編程,調用端按接口簽名處理異常。實現類若拋出更寬泛的檢查型異常,將破壞調用端的異常處理邏輯,編譯器會報錯攔截。
int f() {
try { return 1; }
finally { return 2; } // 直接覆蓋,調用者永遠拿到 2
}
int f() {
try { return 1; }
finally { throw new RuntimeException(); } // 返回值丟失,異常向上拋
}
最佳實踐:
finally 僅做資源關閉/清理,不返回值、不拋異常。finally 中操作可能失敗的資源,內部包 try-catch 並記錄日誌。補充:唯一跳過 finally 的方式是 System.exit(0)(JVM 直接退出)。
原則針對正常業務流程(如用異常代替 if-else 判斷用戶是否存在)。但異常流程(網絡斷開重連、文件讀取失敗重試)必須用異常處理,這是異常的設計初衷。
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(); }
代價:每個方法入口需檢查標誌,增加複雜度。通常優先讓構造器拋異常,失敗快速暴露。
捕獲異常且不再向上拋時,必須確保程序處於已知、可繼續運行的狀態:資源已釋放、數據一致、無副作用殘留。至少要記錄日誌;無法處理時誠實向上拋。
《Practical Java》前章揭示的都是「寫得出但用不好會慘」的核心機制。掌握 equals/hashCode 契約、異常鏈保護、finally 純淨性、構造器失敗語義,能讓你的 Java 代碼從「能跑」進化為「健壯、可維護、可診斷」。