Java Equals与EqualsVerifier的hashCode契约
我对Java中使用库的Java Equals与EqualsVerifier的hashCode契约,java,equals,hashcode,final,equalsverifier,Java,Equals,Hashcode,Final,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()
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
}
}
我尝试使用
运行这个测试,我得到我必须声明equals
和hashCode
方法final,但这是我不想做的事情,因为我可能想在扩展类中声明这两个方法,因为我想在equals
和hashCode
中使用一些子属性
您可以跳过测试EqualsVerifier库中的最终规则吗?还是我遗漏了什么?正确处理这一点非常棘手 解释一种变通方法:
EqualsVerifier.forClass(MyClass.class)
.withRedefinedSubclass(SomeSubclass.class)
.verify();
请注意,要使其起作用,您可能需要在equals中选中
getClass()
,因为工作者可以(或应该)永远不等于人
免责声明:我是equalVerifier的创建者。我才发现这个问题:)
Joachim Sauer提到的变通方法是正确的
让我解释一下为什么EqualVerifier不喜欢您的实现。现在让我们假设Person
不是抽象的;这使示例更简单一些。假设我们有两个Person
对象,如下所示:
Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");
class Person$Proxy extends Person { }
Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");
让我们对这两个对象调用equals
:
boolean b1 = person1.equals(person2); // returns true
boolean b2 = person2.equals(person1); // returns false
b1
为true,因为调用了Person
的equals
方法,它忽略了工作描述
b2
为false,因为调用了Worker
的equals
方法,并且该方法中的instanceof
或getClass()
检入返回false
换句话说,您的equals
方法不再是对称的,这是正确实现equals
的必要条件,根据
您确实可以使用getClass()
来绕过这个问题,但随后会遇到另一个问题。假设您使用Hibernate或模拟框架。这些框架使用字节码操作来创建类的子类。基本上,您将得到如下类:
Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");
class Person$Proxy extends Person { }
Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");
假设您往返访问数据库,如下所示:
Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");
class Person$Proxy extends Person { }
Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");
现在让我们调用等于:
boolean b3 = person1.equals(fetchedPerson); // returns false
boolean b4 = fetchedPerson.equals(person1); // also returns false
b3
和b4
为假,因为person1
和fetchedPerson
属于不同的类别(Person
和Person$Proxy
)equals
现在是对称的,所以至少它遵循契约,但它仍然不是您想要的:fetchedPerson
不再像人那样“行为”。在技术方面:这打破了面向对象编程的基础
有一种方法可以完成所有这些工作,但它相当复杂。(如果你真的想知道:解释怎么做。)为了保持简单,EqualsVerifier建议你将方法设为equals
和hashCode
方法为final。在大多数情况下,这将很好地工作。如果你真的需要,你可以走复杂的路线
在您的情况下,由于Person
是抽象的,您也可以选择不在Person
中实现equals
,而只在Worker
(以及您可能拥有的任何其他子类)中实现!非常感谢你!下一次,我会尽量不等到一年后再回复:)。