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

Java equals 方法深度解析:對稱性陷阱與正確實現模式

     編輯:Java 程式設計入門

equals 契約五大法則

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

JDK 源碼中的「錯誤」示範

java.text.FieldPosition.equals() 使用 instanceof 檢查:


public boolean equals(Object obj) {
    if (obj == null) return false;
    if (!(obj instanceof FieldPosition)) return false;  // 違反對稱性隱患
    FieldPosition other = (FieldPosition) obj;
    // 比較字段...
}

對稱性破壞實測


import java.text.FieldPosition;

public class EqualsSymmetryTest {
    public static void main(String[] args) {
        FieldPosition fp = new FieldPosition(10);
        FieldPosition fpSub = new MyFieldPosition(10);  // 子類
        
        System.out.println("fp.equals(fpSub): " + fp.equals(fpSub));   // true
        System.out.println("fpSub.equals(fp): " + fpSub.equals(fp));   // false!
    }
}

class MyFieldPosition extends FieldPosition {
    int x = 10;
    public MyFieldPosition(int x) { super(x); this.x = x; }
    
    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (!(o instanceof MyFieldPosition)) return false;  // 更嚴格檢查
        return ((MyFieldPosition)o).x == this.x;
    }
}

輸出:

fp.equals(fpSub): true
fpSub.equals(fp): false

原因:父類 instanceof FieldPosition 匹配子類,子類 instanceof MyFieldPosition 不匹配父類。

正確實現三大模式

模式一:最終類(推薦最簡單)


public final class Point {
    private final int x, y;
    
    public Point(int x, int y) { this.x = x; this.y = y; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;  // 精確類匹配
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

模式二:可擴展類 - getClass() 檢查


public class Shape {
    private final String color;
    
    public Shape(String color) { this.color = color; }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;  // 關鍵:精確類匹配
        Shape shape = (Shape) o;
        return Objects.equals(color, shape.color);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(color);
    }
}

// 子類必須覆寫 equals/hashCode 並包含父類字段
public class Circle extends Shape {
    private final double radius;
    
    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }
    
    @Override
    public boolean equals(Object o) {
        if (!super.equals(o)) return false;  // 先檢查父類
        if (getClass() != o.getClass()) return false;
        Circle circle = (Circle) o;
        return Double.compare(radius, circle.radius) == 0;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), radius);
    }
}

模式三:可擴展類 - 模式匹配(Java 16+)


public abstract class Entity {
    private final Long id;
    
    @Override
    public boolean equals(Object o) {
        return o instanceof Entity other 
            && this.getClass() == other.getClass()  // 精確類型
            && Objects.equals(this.id, other.id);
    }
    
    @Override
    public int hashCode() {
        return Objects.hashCode(id);
    }
}

常見錯誤總結

錯誤後果修正
用 instanceof 檢查非 final 類違反對稱性用 getClass() 或 final 類
只比較部分字段違反傳遞性/一致性比較所有標識字段
equals 用 getClass,hashCode 用 instanceof違反 hashCode 契約兩者邏輯一致
可變字段參與 equals放入 Set/Map 後修改導致找不到只用不可變字段
忘記檢查 nullNPE首行 if (o == null) return false

IDE 自動生成 vs 手寫

  • IDEA/Eclipse 生成代碼通常用 getClass(),適用於 final 類或不打算被繼承的類
  • 若設計為可繼承基類,必須手動調整為模式二
  • Java 14+ Record 自動生成正確的 equals/hashCode(基於所有組件)

最佳實踐清單

  1. 類不可變 → final + getClass() 檢查
  2. 類可繼承 → 文檔說明 equals 契約,子類必須遵循
  3. 總是覆寫 hashCode()
  4. 使用 Objects.equals() 處理 null 安全比較
  5. 基本類型用 ==,引用類型用 Objects.equals()
  6. 浮點數用 Double.compare()/Float.compare()
  7. 數組用 Arrays.equals()/Arrays.deepEquals()

正確的 equals 實現是集合框架正常工作的前提,違反契約會導致 HashSet、HashMap、ArrayList.contains() 等出現難以排查的邏輯錯誤。

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