当我读一本Java书时,作者已经说过,在设计一个类时,使用equals()
继承通常是不安全的。例如:
public final class Date {
public boolean equals(Object o) {
// some code here
}
}
在上面的课程中,我们应该放final
,因此其他课程不能从中继承。我的问题是,为什么在允许另一个类从此继承时它是不安全的?
答案 0 :(得分:21)
因为很难(不可能?)使其正确,尤其是symmetric property。
假设您有课程Vehicle
和课程Car extends Vehicle
。如果参数也是Vehicle.equals()
且权重相同,则true
会产生Vehicle
。如果你想实现Car.equals()
,只有当参数也是汽车时才会产生true
,除了重量之外,它还应该比较make,engine等。
现在想象下面的代码:
Vehicle tank = new Vehicle();
Vehicle bus = new Car();
tank.equals(bus); //can be true
bus.equals(tank); //false
如果巧合坦克和公共汽车具有相同的重量,则第一次比较可能会产生true
。但由于坦克不是汽车,将它与汽车相比将总是产生false
。
你几乎没有解决方法:
strict:两个对象相等当且仅当它们具有完全相同的类型(且所有属性相等)时。这很糟糕,例如当你几乎没有子类添加一些行为或装饰原始类。一些框架也是你的类的子类,你没有注意到(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));
答案 1 :(得分:10)
Martin Odersky(Java中的泛型背后的人和当前javac
的原始代码库)在他的书“ Scala中的编程”中有一个很好的章节来解决这个问题。他建议添加canEqual
方法可以解决相等/继承问题。您可以在他的书的第一版中阅读讨论,该书可在线获取:
Chapter 28 of Programming in Scala, First Edition: Object Equality
这本书当然是指Scala,但同样的想法适用于经典Java。对于来自Java背景的人来说,样本源代码不应该太难理解。
<强> 编辑: 强>
看起来Odersky在2009年发表了一篇关于Java中相同概念的文章,它可以在同一个网站上找到:
How to Write an Equality Method in Java
我真的不认为在这个答案中试图总结一下这篇文章是否正确。它涵盖了对象平等的主题,从平等实现中的常见错误到作为等价关系的Java equals
的完整讨论。你应该真的读它。