我对使用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 测试我是否履行了 Person 类中的equals
和hashCode
合同
@Test
public void testEqualsAndHashCodeContract() {
EqualsVerifier.forClass(Person.class).verify();
}
运行这个测试,我知道我必须声明 equals
和 hashCode
方法最终,但这是我不想做的事情,因为我可能想在扩展类中声明这两个方法,因为我想在 equals
和 hashCode
中使用一些孩子的属性。
你能跳过在 EqualsVerifier 库中测试最终规则吗?还是我错过了什么?
免责声明:我是 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
的equals
方法,并且忽略了workDescription
。 b2
为 false,因为调用了Worker
的 equals
方法,并且该方法中的instanceof
或getClass()
检查返回 false。
换句话说,你的equals
方法不再是对称的,这是正确实现equals
的要求,根据Javadoc
您确实可以使用getClass()
来解决此问题,但是您遇到了另一个问题。假设你使用Hibernate或模拟框架。这些框架使用字节码操作来创建类的子类。从本质上讲,你会得到这样的课程:
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
不再像Person
那样"表现"。用技术术语来说:这打破了Liskov替换原则,这是面向对象编程的基础。
有一种方法可以使所有这些工作,但它非常复杂。(如果你真的想知道:这篇文章解释了如何做。为简单起见,EqualsVerifier 建议您将equals
方法和hashCode
方法设为最终方法。在大多数情况下,这将正常工作。如果你真的需要,你总是可以走复杂的路线。
在您的情况下,由于Person
是抽象的,您也可以选择不在 Person
中实现equals
,而仅在Worker
(以及您可能拥有的任何其他子类)中实现。
正确处理是非常棘手的。
EqualsVerifier 的文档解释了一种解决方法:
EqualsVerifier.forClass(MyClass.class)
.withRedefinedSubclass(SomeSubclass.class)
.verify();
请注意,要使其正常工作,您可能需要检查getClass()
等于,因为Worker
可以(或应该)永远不等于Person
。