Equally, hashCode contract with EqualsVerifier

I have some doubts about the equals and hashCode contract in Java using EqualsVerifier .

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 am trying to check if I am executing an equals and hashCode contract in the Person class using EqualsVerifier

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

By running this test, I realized that I must declare the equals and hashCode final methods, but this is what I do not want to do, because I can declare these two methods in extended classes, since I want to use some child attributes in equals and hashCode .

Could you skip to check the final rule in the EqualsVerifier library? Or am I missing something?

+6
source share
2 answers

Disclaimer: I am the creator of EqualsVerifier. I just opened this question :).

Joachim Sauer mentions the workaround correctly.

Let me explain why EqualsVerifier does not like your implementation. Suppose now that Person not abstract; this makes the examples a little easier. Let them say that we have two Person objects, for example:

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

And let equals on both of these objects:

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

b1 true because the Person equals method is called and it ignores workDescription . b2 is false because the Worker equals method is called, and the instanceof or getClass() check is that the method returns false.

In other words, your equals method is no longer symmetrical, and it is a requirement for the equals to be implemented correctly, according to Javadoc .

You can really use getClass() to get around this problem, but then you have another problem. Let's say you use Hibernate or a mocking structure. These structures use bytecode manipulation to subclass your class. Essentially, you get a class like this:

 class Person$Proxy extends Person { } 

So let me say that you make a circular trip to the database, for example:

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

Now call 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 have different classes ( Person and Person$Proxy , to be precise). equals now symmetrical, so at least it follows the contract, but it's still not what you want: fetchedPerson no longer "behaves" like Person . In technical terms: this violates the Liskov replacement principle , which is the basis for object-oriented programming.

There is a way to do all this work, but it's quite complicated. (If you really want to know: this article explains how to do this.) To keep things simple, EqualsVerifier suggests you make the equals and hashCode methods final. In most cases, this will work fine. If you really need to, you can always follow a difficult route.

In your case, since Person is abstract, you can also not implement equals in Person , but only in Worker (and any other subclasses you may have).

+11
source

Obtaining this right is very difficult.

The EqualsVerifier documentation explains a workaround:

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

Note that to do this, you probably need to check getClass() on an equal footing, since a Worker may (or should) never be equal to Person .

+4
source

Source: https://habr.com/ru/post/957897/


All Articles