简体   繁体   English

Equals和hashCode与EqualsVerifier签订合同

[英]Equals and hashCode contract with EqualsVerifier

I have some doubts about equals and hashCode contract in Java using EqualsVerifier library. 我对使用EqualsVerifier库的Java中的equalshashCode契约有些怀疑。

Imagine we have something like this 想象一下,我们有类似的东西

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
    }

}

And the following extended class: 以下扩展课程:

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
    }

}

I try to test whether I fulfill the equals and hashCode contract in the Person class, using EqualsVerifier 我尝试使用EqualsVerifier来测试我是否在Person类中实现了equalshashCode契约

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

Running this test, I get that I have to declare equals and hashCode methods final, but this is something that I don't want to do, because I may want to declare these two methods in the extended classes, since I want to use some child's attributes in equals and hashCode . 运行这个测试,我得到我必须声明equalshashCode方法final,但这是我不想做的事情,因为我可能想在扩展类中声明这两个方法,因为我想使用一些child的属性在equalshashCode

Could you skip for testing the final rule in the EqualsVerifier library? 你可以跳过测试EqualsVerifier库中的最终规则吗? Or am I missing something? 或者我错过了什么?

Disclaimer: I'm the creator of EqualsVerifier. 免责声明:我是EqualsVerifier的创建者。 I only just discovered this question :). 我才发现这个问题:)。

The workaround Joachim Sauer mentions is correct. Joachim Sauer提到的解决方法是正确的。

Let me explain why EqualsVerifier does not like your implementation. 让我解释为什么EqualsVerifier不喜欢你的实现。 Let's pretend for now that Person is not abstract; 让我们假装现在Person不是抽象的; it makes the examples a bit simpler. 它使示例更简单一些。 Let's say we have two Person objects, like this: 假设我们有两个Person对象,如下所示:

Person person1 = new Person("John");
Person person2 = new Worker("John", "CEO of the world");

And let's call equals on both these objects: 让我们在这两个对象上调用equals

boolean b1 = person1.equals(person2); // returns true
boolean b2 = person2.equals(person1); // returns false

b1 is true, because Person 's equals method is called, and it ignores workDescription . b1是真的,因为调用了Personequals方法,它忽略了workDescription b2 is false, because Worker 's equals method is called, and the instanceof or getClass() check in that method returns false. b2为false,因为调用了Workerequals方法,并且该方法中的instanceofgetClass()检查返回false。

In other words, your equals method is no longer symmetric, and this is a requirement for a correct implementation of equals , according to the Javadoc . 换句话说,根据Javadoc ,你的equals方法不再是对称的,这是正确实现equals的要求。

You can indeed use getClass() to get around this problem, but then you run into another problem. 你确实可以使用getClass()来解决这个问题,但是你遇到了另一个问题。 Let's say you use Hibernate, or a mocking framework. 假设您使用Hibernate或模拟框架。 These frameworks use bytecode manipulation to create subclasses of your class. 这些框架使用字节码操作来创建类的子类。 Essentially, you'll get a class like this: 基本上,你会得到这样一个类:

class Person$Proxy extends Person { }

So let's say you make a round trip to the database, like this: 所以假设您要往返数据库,如下所示:

Person person1 = new Person("John");
em.persist(person1);
// ...
Person fetchedPerson = em.find(Person.class, "John");

And now let's call equals : 现在,让我们呼唤equals

boolean b3 = person1.equals(fetchedPerson); // returns false
boolean b4 = fetchedPerson.equals(person1); // also returns false

b3 and b4 are false because person1 and fetchedPerson are of different classes ( Person and Person$Proxy , to be precise). b3b4是假的,因为person1fetchedPerson属于不同的类( fetchedPersonPersonPerson$Proxy )。 equals is symmetric now, so at least it follows the contract, but it's still not what you want: fetchedPerson doesn't "behave" like a Person anymore. equals是对称的,现在,所以至少要遵循合同,但它仍然不是你想要什么: fetchedPerson不“行为”就像一个Person了。 In technical terms: this breaks the Liskov Substitution Principle , which is the basis for Object-Oriented Programming. 在技​​术术语中:这打破了Liskov替换原则 ,这是面向对象编程的基础。

There is a way to make all this work, but it's quite complicated. 有一种方法可以完成所有这些工作,但它非常复杂。 (If you really want to know: this article explains how.) To keep things simple, EqualsVerifier suggests that you make your equals and hashCode methods final. (如果你真的想知道: 这篇文章解释了如何。)为了简单起见,EqualsVerifier建议你使你的equalshashCode方法最终。 In most cases, this will work fine. 在大多数情况下,这将工作正常。 If you really need to, you can always take the complicated route. 如果你真的需要,你可以随时采取复杂的路线。

In your case, since Person is abstract, you could also choose to not implement equals in Person , but only in Worker (and any other subclasses you may have). 在您的情况下,由于Person是抽象的,您也可以选择不在Person实现equals ,而只在Worker (以及您可能拥有的任何其他子类)中实现equals

Getting that right is very tricky. 做到这一点非常棘手。

The documentation of EqualsVerifier explains a workaround: EqualsVerifier的文档解释了一种解决方法:

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

Note that for this to work, you probably need to check getClass() in your equals because a Worker can (or should) never be equal to a Person . 请注意,要使其工作,您可能需要检查equals中的getClass() ,因为Worker可以(或应该)永远不等于Person

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

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