理解equals方法

Understanding equals method

J。 Bloch 在他的 effective Java 中为 equals 方法的实现提供了几个规则。他们在这里:

• Reflexive: For any non-null reference value x, x.equals(x) must return true.

• Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true.

• Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.

• Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

• For any non-null reference value x, x.equals(null) must return false.

但是在书的后面他提到了所谓的里氏替换原则:

The Liskov substitution principle says that any important property of a type should also hold for its subtypes, so that any method written for the type should work equally well on its subtypes

我看不出它与 equals 合同有什么关系。我们是否应该在编写 equals 实现时真正遵守它?

问题是关于实现子类的方法。这是书中的例子:

private static final Set<Point> unitCircle;

static {
    unitCircle = new HashSet<Point>();
    unitCircle.add(new Point(1, 0));
    unitCircle.add(new Point(0, 1));
    unitCircle.add(new Point(-1, 0));
    unitCircle.add(new Point(0, -1));
}

public static boolean onUnitCircle(Point p) {
    return unitCircle.contains(p);
}

public class CounterPoint extends Point {
    private static final AtomicInteger counter = new AtomicInteger();

    public CounterPoint(int x, int y) {
        super(x, y);
        counter.incrementAndGet();
    }

    public int numberCreated() { return counter.get(); }
}

和以下实施:

// Broken - violates Liskov substitution principle (page 40)
@Override public boolean equals(Object o) {
    if (o == null || o.getClass() != getClass())
        return false;
    Point p = (Point) o;
    return p.x == x && p.y == y;
}

好吧,违反了然后呢?没看懂。

我觉得他是想说一个点的特征就是它的坐标。所以你会认为这是真的:

new Point(0, 0).equals(new CounterPoint(0, 0));

因为这两个点的坐标相同,即使它们的类型不同。但是提议的equals方法会return false 因为两个对象有不同的类.

如果您以集合为例,这是正确的:

new LinkedList().equals(new ArrayList());

这两个列表的类型不同,但它们的内容相同(在本例中它们都是空的),因此被认为是相等的。

在 equals 方法中通常有两种检查类型的方法:

选项 1:instanceof

if (! (obj instanceof ThisClass)){
    return false;
}

此选项尊重 Liskov 替换原则。但是你不能在子 classes 中添加与 equals 方法相关的附加属性而不破坏等价关系(自反、对称、传递)的特征。

选项 2:getClass()

if (obj == null || ! this.getClass().equals(obj.getClass())) {
    return false;
}

此选项违反里氏替换原则。但是您 可以 在子 class 中添加与 equals 方法相关的其他属性,而不会破坏等价关系(自反、对称、传递)的特征。

Joshua Bloch 在他的书中警告了这一点 "Effective Java"。

Angelika Langer 但是提到了一种 "mixed-tpye" 比较的方法,如果您可以为其他属性定义默认值:

http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html

缺点是 equals 方法变得相当复杂。

// Broken - violates Liskov substitution principle (page 40)
@Override public boolean equals(Object o) {
    if (o == null || o.getClass() != getClass())
        return false;
    Point p = (Point) o;
    return p.x == x && p.y == y;
}

Ok, violates and what then? I don't understand.

因此,如果您有一个子 class,例如 MyPoint(它可能会添加其他方法,但不会添加其他属性/字段),那么

Point p1 = new Point(x, y);
Point p2 = new MyPoint(x, y);

p1.equals(p2) == false

Set<Point> points = new HashSet<>();
points.add(p1);

points.contains(p2) == false;

虽然这两个对象确实代表了同一个点。

如果您改用选项 1 (instanceof),equals 方法将为 return true。