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

Java equals() 對稱性違反卻能跑?深入解析繼承與 equals 契約陷阱

     編輯:程式設計綜合問答

現象:明明違反對稱性,程式卻「正常」輸出 true/false

T3 t = new T3();      // name="李楠"
Te3 te = new Te3();   // name="劉洋"
System.out.println(t.equals(te));  // true  ← 父類別 equals 忽略子類別欄位
System.out.println(te.equals(t));  // false ← 子類別 equals 要求對方也是 Te3

輸出:
true
false

程式沒有拋出例外、沒有崩潰,JVM 也沒有攔截——**因為 Java 語言規範只規定「契約」,不強制執行**。

equals 契約回顧

Object.equals 文檔明確要求:

  1. 自反性
  2. 對稱性:a.equals(b) == b.equals(a)
  3. 傳遞性
  4. 一致性
  5. 非空性

違反契約**不會導致編譯錯誤或執行期例外**,但會讓**依賴契約的集合框架行為失控**。

實際災難:放入 HashSet/HashMap

Set<T3> set = new HashSet<>();
set.add(t);
set.add(te);          // 邏輯上應視為不同物件
System.out.println(set.size()); // 輸出 1 還是 2?視 hashCode 實作而定,極大機率出錯
System.out.println(set.contains(te)); // 可能回傳 false

因為 HashSet 先比 hashCode 再呼叫 equals,若 t.equals(te) != te.equals(t),集合內部判斷邏輯會產生矛盾,導致:
• 重複元素無法正確去重
• contains/remove 失效
• 迭代器行為不可預期

為什麼這裡會違反對稱性?

// 父類別 T3
public boolean equals(Object o) {
    if (!(o instanceof T3)) return false;  // 允許子類別實例通過
    T3 other = (T3) o;
    return name.equals(other.name) ...;    // 只比較父類別欄位
}

// 子類別 Te3 extends T3
public boolean equals(Object o) {
    if (!super.equals(o)) return false;    // 先呼叫父邏輯
    if (!(o instanceof Te3)) return false; // 但要求對方必須是 Te3
    // 比較子類別欄位...
}

場景:
t.equals(te) → te instanceof T3 為 true → 只比父欄位 → 相同 → true
te.equals(t) → super.equals(t) 為 true → t instanceof Te3 為 false → false

正確修正模式

方案一:使用 getClass() 而非 instanceof(推薦)

// 父類別
@Override public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false; // 嚴格同類別
    T3 other = (T3) o;
    return Objects.equals(name, other.name) ...;
}

優點:簡單、嚴格滿足對稱性與傳遞性。
缺點:子類別無法與父類別互為 equals(通常這正是想要的)。

方案二:若必須支援跨類別比較,用「標準演算法」

參考 Effective Java Item 10:
1. 父類別 equals 宣告為 final,不允許子類別覆寫。
2. 子類別若需比較額外欄位,改用 **組合** 而非繼承,或提供 equalsExcludingSubclassFields 等輔助方法。
3. 真正需要多型比較時,定義介面 Equatable<T> 並在介面層級實作對稱比較邏輯。

總結

「能跑」≠「正確」。equals 契約是集合框架正確運作的前提,違反對稱性如同在炸彈上跳舞——平時不炸,關鍵時刻(進 HashMap、做去重、序列化)炸給你看。
**鐵律:覆寫 equals 時,務必配合 hashCode,並用 getClass() 守住類別邊界,除非你有極充分理由且完整處理跨類別對稱性。**

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