12

当我阅读一本 Java 书籍时,作者曾说过,在设计一个类时,使用equals()继承通常是不安全的。例如:

public final class Date {
    public boolean equals(Object o) {
         // some code here
    }
}

在上面的类中,我们应该放final,所以其他类不能继承。我的问题是,为什么允许另一个类继承它是不安全的?

4

2 回答 2

24

因为很难(不可能?)使它正确,尤其是对称属性

假设你有 classVehicle和 class Car extends Vehicle。如果参数也是 a并且具有相同的权重,则产生Vehicle.equals()。如果你想实现它,只有当参数也是汽车时才应该产生,除了重量,它还应该比较品牌、引擎等。trueVehicleCar.equals()true

现在想象下面的代码:

Vehicle tank = new Vehicle();
Vehicle bus = new Car();
tank.equals(bus);  //can be true
bus.equals(tank);  //false

true如果碰巧油箱和公共汽车具有相同的重量,则第一次比较可能会产生。但是由于坦克不是汽车,因此将其与汽车进行比较总是会产生false

你有几个解决方法:

  • 严格:两个对象相等当且仅当它们具有完全相同的类型(并且所有属性都相等)。这很糟糕,例如,当您创建子类以添加一些行为或装饰原始类时。一些框架也在你没有注意到的情况下对你的类进行子类化(Hibernate、带有 CGLIB 代理的 Spring AOP ......)

  • 松散的:如果两个对象的类型“兼容”并且它们具有相同的内容(语义上),则它们是相等的。例如,如果两个集合包含相同的元素,则它们是相等的,一个是HashSet另一个是无关紧要TreeSet(感谢@veer指出这一点)。

    这可能会产生误导。取两个LinkedHashSets(插入订单作为合同的一部分很重要)。然而,由于equals()只考虑了原始Set合同,true即使对于明显不同的对象,比较也会产生:

    Set<Integer> s1 = new LinkedHashSet<Integer>(Arrays.asList(1, 2, 3));
    Set<Integer> s2 = new LinkedHashSet<Integer>(Arrays.asList(3, 2, 1));
    System.out.println(s1.equals(s2));
    
于 2012-09-02T20:14:32.557 回答
11

Martin Odersky(Java 中的泛型和当前的原始代码库背后的人)在他的《 Scala 编程》javac一书中有一个很好的章节来解决这个问题。他建议添加一个方法可以解决相等/继承问题。您可以阅读他的书第一版中的讨论,该书可在线获取:canEqual

Scala 编程,第一版的第 28 章:对象平等

这本书当然指的是 Scala,但同样的想法也适用于经典的 Java。对于具有 Java 背景的人来说,示例源代码应该不难理解。

编辑:

看起来 Odersky 早在 2009 年就在 Java 中发表了一篇关于相同概念的文章,并且可以在同一个网站上找到它:

如何在 Java 中编写等式方法

我真的不认为试图在这个答案中总结这篇文章会做到公正。它深入涵盖了对象相等的主题,从相等实现中的常见错误到对 Javaequals作为等价关系的全面讨论。你真的应该读一下。

于 2012-09-02T20:24:30.900 回答