我对使用equals库的Java中的hashCode和EqualsVerifier契约有一些疑问。
想象一下我们有这样的东西
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在Person类中测试是否履行了EqualsVerifier和契约。
@Test
public void testEqualsAndHashCodeContract() {
EqualsVerifier.forClass(Person.class).verify();
}在运行这个测试时,我必须声明equals和hashCode方法为final,但这是我不想做的事情,因为我可能希望在扩展类中声明这两个方法,因为我想在equals和hashCode中使用一些子属性。
您可以跳过测试EqualsVerifier库中的最终规则吗?还是我漏掉了什么?
发布于 2014-12-01 21:16:36
免责声明:我是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 falseb1为true,因为Person的equals方法被调用,并且它忽略了workDescription。b2是false,因为Worker的equals方法被调用,而在该方法中的instanceof或getClass()检查返回false。
换句话说,根据equals,您的equals方法不再是对称的,这是正确实现equals的必要条件。
您确实可以使用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 falseb3和b4是错误的,因为person1和fetchedPerson属于不同的类(确切地说,是Person和Person$Proxy )。equals现在是对称的,所以至少它遵循契约,但它仍然不是您想要的:fetchedPerson不再像Person那样“表现”。从技术上讲:这打破了Liskov代换原理,因为它是面向对象编程的基础。
有办法使所有这些工作,但它是相当复杂的。(如果你真的想知道:这篇文章解释了怎么做的。)为了保持简单,EqualsVerifier建议您将equals和hashCode方法确定为最终方法。在大多数情况下,这会很好。如果你真的需要的话,你总是可以走复杂的路线。
在您的示例中,由于Person是抽象的,所以您也可以选择在Person中不实现equals,而只能在Worker (和可能拥有的任何其他子类)中实现。
发布于 2013-11-12 10:23:06
做得对是很棘手的。
EqualsVerifier文档解释了一个解决办法:
EqualsVerifier.forClass(MyClass.class)
.withRedefinedSubclass(SomeSubclass.class)
.verify();请注意,要使此操作正常,您可能需要在等于中签入getClass(),因为Worker可能(或应该)永远不等于Person。
https://stackoverflow.com/questions/19926486
复制相似问题