java - 理解equals方法

标签 java equals

J. Bloch 在他的有效 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.

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

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;
}

好吧,违反了,然后呢?我不明白。

最佳答案

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

选项一:instanceof

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

此选项尊重里氏替换原则。但是您不能在不破坏等价关系(自反、对称、传递)特征的情况下,在与 equals 方法相关的子类中添加其他属性。

选项 2:getClass()

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

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

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

但是 Angelika Langer 提到了一种“混合类型”比较的方法,如果您可以为其他属性定义默认值:

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.

因此,如果您有一个子类,例如 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 方法将返回 true。

关于java - 理解equals方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30998600/

相关文章:

java - 不理解有效 XML 字符集的正则表达式

java - 使用 ProcessBuilder 在我的 Android 应用程序中启动服务,但它仅以 "su"(root) 开头

java - 用NULL分割UTF16字符串的简单方法

java - equals() 方法如何在字符串缓冲区中工作?

java - 如何避免 Object.equals 方法出现多个 if else block ?

具有相同返回类型的 Java 方法重载错误?

javascript - 使用 window.open 方法

java - java中检查两个字符串是否相等

Java:如何检查实例属于哪个对象?

java - 对于没有字段的类,equals() 的合适实现是什么?