繁体   English   中英

Equals和hashCode与EqualsVerifier签订合同

[英]Equals and hashCode contract with 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来测试我是否在Person类中实现了equalshashCode契约

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

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

你可以跳过测试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是真的,因为调用了Personequals方法,它忽略了workDescription b2为false,因为调用了Workerequals方法,并且该方法中的instanceofgetClass()检查返回false。

换句话说,根据Javadoc ,你的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 false

b3b4是假的,因为person1fetchedPerson属于不同的类( fetchedPersonPersonPerson$Proxy )。 equals是对称的,现在,所以至少要遵循合同,但它仍然不是你想要什么: fetchedPerson不“行为”就像一个Person了。 在技​​术术语中:这打破了Liskov替换原则 ,这是面向对象编程的基础。

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

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

做到这一点非常棘手。

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM