Equals和hashCode与EqualsVerifier签订合同

时间:2013-11-12 10:19:23

标签: java equals hashcode final equalsverifier

我对使用EqualsVerifier库的Java equalshashCode合同有所怀疑。

想象一下,我们有类似的东西

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

测试我是否符合类中的equalshashCode合同
    @Test
    public void testEqualsAndHashCodeContract() {
        EqualsVerifier.forClass(Person.class).verify();
    }

运行此测试,我得知我必须声明equalshashCode方法最终,但这是我不想做的事情,因为我可能想要声明这两个方法在扩展类中,因为我想在equalshashCode中使用一些子属性。

您可以跳过测试EqualsVerifier库中的最终规则吗?或者我错过了什么?

2 个答案:

答案 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}}方法被调用,它忽略了equalsworkDescription为false,因为调用了b2 Worker方法,并且该方法中的equalsinstanceof检查返回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是错误的,因为b4person1属于不同的类别(fetchedPersonPerson,确切地说)。 Person$Proxy现在是对称的,所以至少它遵循合同,但它仍然不是你想要的:equals没有"表现"比如fetchedPerson了。在技​​术术语中:这打破了Liskov Substitution Principle,这是面向对象编程的基础。

有一种方法可以完成所有这些工作,但它非常复杂。 (如果你真的想知道:this article解释了如何。)为了简单起见,EqualsVerifier建议你最终使Personequals方法。在大多数情况下,这将工作正常。如果你真的需要,你可以随时采取复杂的路线。

在您的情况下,由于hashCode是抽象的,您也可以选择不在Person中实现equals,但只能在Person(以及您可以使用的任何其他子类)有)。

答案 1 :(得分:4)

做到这一点非常棘手。

The documentation of EqualsVerifier解释了一种解决方法:

EqualsVerifier.forClass(MyClass.class)
    .withRedefinedSubclass(SomeSubclass.class)
    .verify();

请注意,为了实现此目的,您可能需要检查等于getClass(),因为Worker可以(或应该)永远不等于Person