8

我对使用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类中的equalshashCode合同

    @Test
    public void testEqualsAndHashCodeContract() {
        EqualsVerifier.forClass(Person.class).verify();
    }

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

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

4

2 回答 2

17

免责声明:我是 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'sequals方法被调用,它忽略了workDescription. b2为 false,因为调用了Worker' 的equals方法,并且该方法中的instanceoforgetClass()签入返回 false。

换句话说,根据Javadocequals ,您的方法不再是对称的,这是正确实现 的要求。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 false

b3andb4是错误的,因为person1andfetchedPerson属于不同的类别(准确地说是Personand Person$Proxy)。equals现在是对称的,所以至少它遵循合同,但它仍然不是你想要的:不再fetchedPerson像 a 那样“表现” Person。用技术术语来说:这打破了Liskov 替换原则,这是面向对象编程的基础。

有一种方法可以使所有这些工作,但它相当复杂。(如果你真的想知道:这篇文章解释了如何做。)为了简单起见,EqualsVerifier 建议你将你的equalshashCode方法设为final。在大多数情况下,这将正常工作。如果你真的需要,你总是可以走复杂的路线。

在您的情况下,由于Person是抽象的,您也可以选择不实现equalsin Person,而只实现 in Worker(以及您可能拥有的任何其他子类)。

于 2014-12-01T21:16:36.547 回答
4

做到这一点非常棘手。

EqualsVerifier 的文档解释了一种解决方法:

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

请注意,要使其正常工作,您可能需要检查getClass()您的 equals 因为 aWorker可以(或应该)永远不会等于 a Person

于 2013-11-12T10:23:06.233 回答