了解equals方法

时间:2015-06-23 09:12:36

标签: java equals

学家Bloch在他有效的Java中为equals方法的实现提供了几条规则。他们在这里:

  

•自反:对于任何非空参考值x,x.equals(x)必须   返回true。

     

•对称:对于任何非空参考值x和y,   当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

     

•Transitive:对于任何非空引用值x,y,z,if   x.equals(y)返回true,y.equals(z)返回true,然后   x.equals(z)必须返回true。

     

•一致:对于任何非空引用   值x和y,x.equals(y)的多次调用始终如一   如果没有使用任何信息,则返回true或始终返回false   in equals比较对象被修改。

     

•对于任何非null   引用值x,x.equals(null)必须返回false。

但后来他在书中提到了所谓的利斯科夫替代原则:

  

Liskov替代原则说任何重要的财产   一个类型也应该为其子类型保留,以便编写任何方法   该类型应该在其子类型上同样有效

我不知道它与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;
}

好的,违反了什么呢?我不明白。

2 个答案:

答案 0 :(得分:6)

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

选项1:instanceof

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

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

选项2:getClass()

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

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

Joshua Bloch在他的书#34; Effective Java&#34;。

中警告过这一点

然而Angelika Langer提到了一种方式,混合tpye&#34;比较,如果您可以为其他属性定义默认值:

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

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

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

好的,违反了什么呢?我不明白。

因此,如果您有一个子类,如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。

答案 1 :(得分:1)

我认为他试图说一个点的特征是它的坐标。所以你会期望这是真的:

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

因为这两个点具有相同的坐标,即使它们没有相同的类型。但是建议的equals方法将返回false,因为这两个对象具有不同的类。

如果你想到集合,例如,这是真的:

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

这两个列表的类型不同,但它们具有相同的内容(在这种情况下它们都是空的),因此被认为是相同的。