我对使用EqualsVerifier库的Java equals
和hashCode
合同有所怀疑。
想象一下,我们有类似的东西
public abstract class Person {
protected String name;
@Override
public boolean equals(Object obj) {
// only name is taken into account
}
@Override
public int hashCode() {
// only name is taken into account
}
}
以下扩展课程:
public final class Worker extends Person {
private String workDescription;
@Override
public final boolean equals(Object obj) {
// name and workDescription are taken into account
}
@Override
public final int hashCode() {
// name and workDescription are taken into account
}
}
我尝试使用EqualsVerifier
测试我是否符合人类中的equals
和hashCode
合同
@Test
public void testEqualsAndHashCodeContract() {
EqualsVerifier.forClass(Person.class).verify();
}
运行此测试,我得知我必须声明equals
和hashCode
方法最终,但这是我不想做的事情,因为我可能想要声明这两个方法在扩展类中,因为我想在equals
和hashCode
中使用一些子属性。
您可以跳过测试EqualsVerifier库中的最终规则吗?或者我错过了什么?
答案 0 :(得分:12)
免责声明:我是EqualsVerifier的创建者。我刚刚发现了这个问题:)。
Joachim Sauer提到的解决方法是正确的。
让我解释为什么EqualsVerifier不喜欢你的实现。让我们假装Person
不是抽象的;它使示例更简单一些。我们假设我们有两个Person
个对象,如下所示:
Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");
让我们在这两个对象上调用equals
:
boolean b1 = person1.equals(person2); // returns true
boolean b2 = person2.equals(person1); // returns false
b1
是正确的,因为Person
的{{1}}方法被调用,它忽略了equals
。 workDescription
为false,因为调用了b2
Worker
方法,并且该方法中的equals
或instanceof
检查返回false。
换句话说,getClass()
方法不再是对称的,根据the Javadoc,这是正确实施equals
的要求。
您确实可以使用equals
来解决此问题,但之后又遇到了另一个问题。让我们假设您使用Hibernate或模拟框架。这些框架使用字节码操作来创建类的子类。从本质上讲,你会得到一个这样的课程:
getClass()
所以,让我们说你往返数据库,如下:
class Person$Proxy extends Person { }
现在让我们来电话Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");
:
equals
boolean b3 = person1.equals(fetchedPerson); // returns false
boolean b4 = fetchedPerson.equals(person1); // also returns false
和b3
是错误的,因为b4
和person1
属于不同的类别(fetchedPerson
和Person
,确切地说)。 Person$Proxy
现在是对称的,所以至少它遵循合同,但它仍然不是你想要的:equals
没有"表现"比如fetchedPerson
了。在技术术语中:这打破了Liskov Substitution Principle,这是面向对象编程的基础。
有一种方法可以完成所有这些工作,但它非常复杂。 (如果你真的想知道:this article解释了如何。)为了简单起见,EqualsVerifier建议你最终使Person
和equals
方法。在大多数情况下,这将工作正常。如果你真的需要,你可以随时采取复杂的路线。
在您的情况下,由于hashCode
是抽象的,您也可以选择不在Person
中实现equals
,但只能在Person
(以及您可以使用的任何其他子类)有)。
答案 1 :(得分:4)
做到这一点非常棘手。
The documentation of EqualsVerifier解释了一种解决方法:
EqualsVerifier.forClass(MyClass.class)
.withRedefinedSubclass(SomeSubclass.class)
.verify();
请注意,为了实现此目的,您可能需要检查等于getClass()
,因为Worker
可以(或应该)永远不等于Person
。