Контракт Equals и hashCode с EqualsVerifier
у меня есть некоторые сомнения о equals
и hashCode
контракт на Java с использованием EqualsVerifier библиотека.
представьте, что у нас есть что-то вроде этого
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
}
}
Я пытаюсь проверить, выполняю ли я equals
и hashCode
контракт человек класс, используя EqualsVerifier
@Test
public void testEqualsAndHashCodeContract() {
EqualsVerifier.forClass(Person.class).verify();
}
запуск этого теста, я получаю, что я должен объявить equals
и hashCode
методы final, но это то, что я не хочу делать, потому что я могу объявить эти два метода в расширенных классах, так как я хочу использовать некоторые атрибуты ребенка в equals
и hashCode
.
не могли бы вы пропустить тестирование окончательного правила в библиотеке EqualsVerifier? Или я что-то упускаю?
2 ответов
отказ от ответственности: я создатель EqualsVerifier. Я только что обнаружил этот вопрос :).
обходной путь Иоахим Зауэр упоминает правильно.
позвольте мне объяснить, почему 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
' s equals
вызывается метод, и он игнорирует workDescription
. b2
ложно, потому что Worker
' s equals
вызывается метод, и instanceof
или getClass()
проверьте, что метод возвращает false.
другими словами, ваш equals
метод больше не симметричен, и это требование для правильной реализации equals
, по данным в Javadoc.
вы действительно можете использовать 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
b3
и b4
не соответствуют действительности, поскольку person1
и fetchedPerson
относятся к разным классам (Person
и Person$Proxy
, если быть точным). equals
теперь симметрично, поэтому, по крайней мере, он следует контракту, но это все еще не то, что вы хотите: fetchedPerson
не "ведет себя" как
получить это право очень сложно.
документация EqualsVerifier объясняет обходной путь:
EqualsVerifier.forClass(MyClass.class)
.withRedefinedSubclass(SomeSubclass.class)
.verify();
обратите внимание, что для этого вам, вероятно, нужно проверить getClass()
в равных потому что Worker
может (или должен) никогда не быть равен Person
.