当我阅读一本 Java 书籍时,作者曾说过,在设计一个类时,使用equals()
继承通常是不安全的。例如:
public final class Date {
public boolean equals(Object o) {
// some code here
}
}
在上面的类中,我们应该放final
,所以其他类不能继承。我的问题是,为什么允许另一个类继承它是不安全的?
因为很难(不可能?)使它正确,尤其是对称属性。
假设你有 classVehicle
和 class Car extends Vehicle
。如果参数也是 a并且具有相同的权重,则产生Vehicle.equals()
。如果你想实现它,只有当参数也是汽车时才应该产生,除了重量,它还应该比较品牌、引擎等。true
Vehicle
Car.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指出这一点)。
这可能会产生误导。取两个LinkedHashSet
s(插入订单作为合同的一部分很重要)。然而,由于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));
Martin Odersky(Java 中的泛型和当前的原始代码库背后的人)在他的《 Scala 编程》javac
一书中有一个很好的章节来解决这个问题。他建议添加一个方法可以解决相等/继承问题。您可以阅读他的书第一版中的讨论,该书可在线获取:canEqual
这本书当然指的是 Scala,但同样的想法也适用于经典的 Java。对于具有 Java 背景的人来说,示例源代码应该不难理解。
编辑:
看起来 Odersky 早在 2009 年就在 Java 中发表了一篇关于相同概念的文章,并且可以在同一个网站上找到它:
我真的不认为试图在这个答案中总结这篇文章会做到公正。它深入涵盖了对象相等的主题,从相等实现中的常见错误到对 Javaequals
作为等价关系的全面讨论。你真的应该读一下。