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

Java 核心技巧:深入解析 equals、hashCode 與 toString 正確實作

     編輯:Java 綜合教學

前言

在 Java 開發中,equals、hashCode 與 toString 是 Object 類別的三大核心方法,正確覆寫它們是編寫高品質程式碼的基石。本文將透過實際案例,深入探討這些方法的常見陷阱與最佳實踐,並介紹 package-info.java 的妙用,以及為何不應手動呼叫垃圾回收。

建議 47:使用 getClass 進行型別判斷,避免違反對稱性與傳遞性

當子類別覆寫 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 契約。

建議 48:覆寫 equals 必須覆寫 hashCode

若類別作為 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 計算。

建議 49:覆寫 toString 方法,提升除錯效率

預設 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();
}

建議 50:善用 package-info.java 管理套件層級資訊

package-info.java 是一個特殊的類別檔案,主要用途三:

  1. 集中管理套件內常數與友好類別:減少 package-private 類別散落。
  2. 提供套件層級註解:如 @Namespace、@FilterDef 等。
  3. 撰寫套件文件:Javadoc 會將其內容作為套件文件首頁,實現程式碼與文件同步維護。
/**
 * 訂單處理核心模組
 * @version 2.1
 * @since 2023-05
 */
package com.example.order;

class OrderConstants {
    static final String DEFAULT_CURRENCY = "TWD";
}

建議 51:切勿手動呼叫 System.gc()

System.gc() 會觸發 Full GC,導致所有應用執行緒暫停(Stop-the-world),對高併發系統極其危險。JVM 內建的分代收集器(如 G1、ZGC)已具備優秀的自適應調優能力,手動干擾反而破壞其節奏。遇到 OOM 應透過堆積分析工具(MAT、JVisualVM)定位記憶體洩漏根因,而非依賴 GC。

總結

  • equals 使用 getClass 判斷型別,保證對稱性與傳遞性。
  • 覆寫 equals 必須覆寫 hashCode,且欄位一致。
  • 覆寫 toString 讓除錯事半功倍。
  • 活用 package-info.java 治理套件架構。
  • 信任 JVM 垃圾收集器,勿手動觸發 GC。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved