【问题标题】:Equals and hashCode contract with EqualsVerifier使用 EqualsVerifier 的 Equals 和 hashCode 合同
【发布时间】:2013-11-12 10:19:22
【问题描述】:

我对使用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,但这是我不想做的事情,因为我可能想在扩展类中声明这两个方法, 因为我想在equalshashCode 中使用一些孩子的属性。

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

【问题讨论】:

    标签: java equals hashcode final equalsverifier


    【解决方案1】:

    免责声明:我是 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 方法,它忽略了workDescriptionb2 为 false,因为调用了 Workerequals 方法,而 instanceofgetClass() 签入该方法返回 false。

    换句话说,您的equals 方法不再是对称的,根据the Javadoc,这是正确实现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 属于不同的类(准确地说是PersonPerson$Proxy)。 equals 现在是对称的,所以至少它遵循合同,但它仍然不是你想要的:fetchedPerson 不再像 Person 那样“表现”。用技术术语来说:这打破了Liskov Substitution Principle,这是面向对象编程的基础。

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

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

    【讨论】:

    • 哇!非常感谢!
    • 下一次,我尽量不要等一年再回复:)。
    【解决方案2】:

    做到这一点非常棘手。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-09
      • 2014-05-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多