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

Java equals 方法深度解析:對象相等比較的規範與陷阱

     編輯:Java 相關知識

equals 契約:五大核心規則

Object.equals() 必須滿足:

  1. 自反性:x.equals(x) == true
  2. 對稱性:x.equals(y) == true ⇒ y.equals(x) == true
  3. 傳遞性:x.equals(y) && y.equals(z) ⇒ x.equals(z)
  4. 一致性:多次調用結果不變(前提對象未修改)。
  5. 非空性:x.equals(null) == false

此外,equals 與 hashCode 必須一致:相等對象哈希碼必須相等。

標準實現模板 (JDK FieldPosition 示例)

public boolean equals(Object obj) {
    if (obj == null) return false;
    if (!(obj instanceof FieldPosition)) return false;
    FieldPosition other = (FieldPosition) obj;
    if (attribute == null) {
        if (other.attribute != null) return false;
    } else if (!attribute.equals(other.attribute)) return false;
    return (beginIndex == other.beginIndex
         && endIndex == other.endIndex
         && field == other.field);
}

instanceof 陷阱:破壞對稱性

看似正確的 instanceof 檢查在繼承場景下會失效:

class MyTest extends FieldPosition {
    int x = 10;
    public MyTest(int x) { super(x); this.x = x; }
    public boolean equals(Object o) {
        if (o == null) return false;
        if (!(o instanceof MyTest)) return false; // 只比較同類
        return ((MyTest)o).x == this.x;
    }
}

// 測試
FieldPosition fp = new FieldPosition(10);
FieldPosition fp1 = new MyTest(10);
System.out.println(fp.equals(fp1));  // true  (父類 instanceof 子類 為 true)
System.out.println(fp1.equals(fp));  // false (子類 instanceof 父類 為 true,但父類非 MyTest)

結果:對稱性被破壞!

根本原因分析

  • instanceof 語義:"子類是父類的實例" ⇒ 子類 instanceof 父類返回 true。
  • 父類 equals 用 instanceof FieldPosition 檢查,子類實例通過檢查,進入字段比較(可能因缺少子類字段而返回 true)。
  • 子類 equals 用 instanceof MyTest 檢查,父類實例失敗,直接返回 false。

正確解決方案

方案一:final 類安全使用 instanceof

類聲明 final 時無繼承風險,可放心用 instanceof 優化。

方案二:精確類型比較 (推薦通用做法)

public boolean equals(Object obj) {
    if (this == obj) return true;           // 同一引用快速返回
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false; // 精確類型匹配
    MyClass other = (MyClass) obj;
    // 比較所有關鍵字段...
    return field1 == other.field1 && Objects.equals(field2, other.field2);
}

關鍵點:使用 getClass() != obj.getClass() 而非 obj.getClass() != MyClass.class,確保子類未覆寫 equals 時也能正確工作(子類實例 getClass() 返回子類 Class,不等於父類 Class,返回 false,避免錯誤比較)。

方案三:可控繼承場景的 getClass 比較

若設計允許子類比較(如值對象),需在父類文檔說明,子類必須覆寫 equals 並調用 super.equals(),但極易出錯,一般不推薦。

最佳實踐總結

  1. 覆寫 equals 必須覆寫 hashCode。
  2. 優先使用 getClass() 精確類型檢查,除非類是 final。
  3. 逐個比較關鍵字段:基本類型用 ==,引用類型用 Objects.equals() (Null 安全)。
  4. 使用 IDE 自動生成或 Lombok @EqualsAndHashCode 減少手寫錯誤。
  5. 不可變對象適合作為 Map 鍵/Set 元素,確保 equals/hashCode 穩定。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved