当我读了一本Java书,作者曾表示,设计一个类时,它通常不安全使用equals()
与继承。 例如:
public final class Date {
public boolean equals(Object o) {
// some code here
}
}
在上面的类,我们应该把final
,所以其他类不能从这个继承。 我的问题是,为什么当允许从该另一个类继承,它是不安全的?
当我读了一本Java书,作者曾表示,设计一个类时,它通常不安全使用equals()
与继承。 例如:
public final class Date {
public boolean equals(Object o) {
// some code here
}
}
在上面的类,我们应该把final
,所以其他类不能从这个继承。 我的问题是,为什么当允许从该另一个类继承,它是不安全的?
因为它是硬(不可能?)作出正确选择,尤其是对称性 。
假设你有一流的Vehicle
及类Car extends Vehicle
。 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
。
你有几个变通办法:
严格:两个对象如果相等且仅当它们拥有完全相同的类型(以及所有属性都相等)。 这是不好的,例如,当你继承勉强添加一些行为或装饰的原始类。 一些框架是子类化你的类以及没有你注意到(休眠,Spring AOP的使用CGLIB代理...)
宽松:如果他们的类型是“兼容”,他们有相同的内容(语义)两个对象是相等的。 例如,两个集合相等,如果它们包含相同的元素,它并不重要,一个是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));
马丁·奥德斯基(在Java泛型和原来的代码库当前背后的人javac
) 在斯卡拉在他的书中编程一个不错的章解决这一问题。 他建议增加一个canEqual
方法可以解决平等/继承问题。 您可以阅读他的书,这是网上提供的第一版的讨论:
在Scala中,第一版编程的第28章:对象平等
这本书当然是指斯卡拉的,但同样的想法适用于经典的Java。 示例源代码应该不会太困难的人从Java的背景来理解。
编辑:
它看起来像Odersky的早在2009年发表了在Java中同一概念的文章,而且是适用相同的网站:
如何用Java编写的平等法
我真的不认为试图总结这个答案的文章会做到公正。 它深入探讨了对象平等的话题,从平等的实现作出Java的充分讨论常见的错误, equals
为等价关系。 你真的应该只是阅读。