在 Java 開發中,equals、hashCode 與 toString 是 Object 類別的三大核心方法,正確覆寫它們是編寫高品質程式碼的基石。本文將透過實際案例,深入探討這些方法的常見陷阱與最佳實踐,並介紹 package-info.java 的妙用,以及為何不應手動呼叫垃圾回收。
當子類別覆寫 equals 時,若父類別使用 instanceof 判斷型別,會導致嚴重的邏輯錯誤。以下範例展示 Employee 繼承 Person 的情境:
public class Employee extends Person {
private int id;
// 建構子、getter/setter 省略
@Override
public boolean equals(Object obj) {
if (obj instanceof Employee) {
Employee e = (Employee) obj;
return super.equals(obj) && e.getId() == id;
}
return false;
}
}
class Person {
private String name;
// 建構子、getter/setter 省略
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person p = (Person) obj;
if (p.getName() == null || name == null) return false;
return name.equalsIgnoreCase(p.getName());
}
return false;
}
}
測試碼:
Employee e1 = new Employee("張三", 100);
Employee e2 = new Employee("張三", 1000);
Person p1 = new Person("張三");
System.out.println(p1.equals(e1)); // true
System.out.println(p1.equals(e2)); // true
System.out.println(e1.equals(e2)); // false
結果違反 對稱性(p1.equals(e1) 為 true,但 e1.equals(p1) 為 false)與 傳遞性(p1.equals(e1) 且 p1.equals(e2),但 e1.equals(e2) 為 false)。
解決方案:改用 getClass() == this.getClass() 進行嚴格型別比較:
@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == this.getClass()) {
Person p = (Person) obj;
// 比較邏輯...
}
return false;
}
這確保只有完全相同類別的實例才會被視為相等,符合 equals 契約。
若類別作為 HashMap 的 Key 或存入 HashSet,hashCode 未覆寫會導致無法正確查找。範例:
Map<Person, Object> map = new HashMap<>();
map.put(new Person("張三"), new Object());
System.out.println(map.containsKey(new Person("張三"))); // false!
原因在於 HashMap 先依 hashCode 定位桶位,再用 equals 比較。未覆寫 hashCode 時,兩個「張三」物件的雜湊碼不同,落入不同桶位,導致查找失敗。
正確實作(推薦使用 Apache Commons Lang 的 HashCodeBuilder):
@Override
public int hashCode() {
return new HashCodeBuilder().append(name).toHashCode();
}
原則:equals 判斷相等的欄位,必須參與 hashCode 計算。
預設 toString 輸出如 Person@188edd79,毫無可讀性。覆寫後可輸出有意義的資訊:
@Override
public String toString() {
return String.format("%s.name=%s", this.getClass().getSimpleName(), name);
}
輸出範例:Person.name=張三。屬性較多時,可使用 ToStringBuilder:
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
.append("name", name)
.append("id", id)
.toString();
}
package-info.java 是一個特殊的類別檔案,主要用途三:
package-private 類別散落。@Namespace、@FilterDef 等。/**
* 訂單處理核心模組
* @version 2.1
* @since 2023-05
*/
package com.example.order;
class OrderConstants {
static final String DEFAULT_CURRENCY = "TWD";
}
System.gc() 會觸發 Full GC,導致所有應用執行緒暫停(Stop-the-world),對高併發系統極其危險。JVM 內建的分代收集器(如 G1、ZGC)已具備優秀的自適應調優能力,手動干擾反而破壞其節奏。遇到 OOM 應透過堆積分析工具(MAT、JVisualVM)定位記憶體洩漏根因,而非依賴 GC。
equals 使用 getClass 判斷型別,保證對稱性與傳遞性。equals 必須覆寫 hashCode,且欄位一致。toString 讓除錯事半功倍。package-info.java 治理套件架構。