Переопределение equals() и hashCode() в подклассах... рассмотрение суперполей
Есть ли определенное правило о том, как переопределять equals()
и hashCode()
в подклассах с учетом суперполя? зная, что существует много параметров: суперполя является частным/общедоступным, с/без getter...
Например, Netbeans генерирует equals(), и hashCode() не будет рассматривать суперполя... и
new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))
вернет true: (
public class Hominidae {
public String gender;
public String weight;
public String height;
public Hominidae(String gender, String weight, String height) {
this.gender = gender;
this.weight = weight;
this.height = height;
}
...
}
public class HomoSapiens extends Hominidae {
public String name;
public String faceBookNickname;
public HomoSapiens(String gender, String weight, String height,
String name, String facebookId) {
super(gender, weight, height);
this.name = name;
this.faceBookNickname = facebookId;
}
...
}
Если вы хотите, чтобы Netbeans сгенерировали equals() и hashCode():
public class Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Hominidae other = (Hominidae) obj;
if ((this.gender == null) ? (other.gender != null) : !this.gender.equals(other.gender)) {
return false;
}
if ((this.weight == null) ? (other.weight != null) : !this.weight.equals(other.weight)) {
return false;
}
if ((this.height == null) ? (other.height != null) : !this.height.equals(other.height)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 5;
hash = 37 * hash + (this.gender != null ? this.gender.hashCode() : 0);
hash = 37 * hash + (this.weight != null ? this.weight.hashCode() : 0);
hash = 37 * hash + (this.height != null ? this.height.hashCode() : 0);
return hash;
}
}
public class HomoSapiens extends Hominidae {
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.faceBookNickname == null) ? (other.faceBookNickname != null) : !this.faceBookNickname.equals(other.faceBookNickname)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
}
Ответы
Ответ 1
Дети не должны изучать частных членов своих родителей.
Но, очевидно, все существенные поля должны учитываться для равенства и хэширования.
К счастью, вы можете легко выполнить оба правила.
Предполагая, что вы не застряли с использованием генерируемых NetBeans равных и hashcode, вы можете изменить метод hominidae equals, чтобы использовать instanceof сравнения, а не равенство классов, а затем использовать его прямо. Что-то вроде этого:
@Override
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (getClass() != obj.getClass()) { return false; }
if (! super.equals(obj)) return false;
else {
// compare subclass fields
}
Конечно, hashcode легко:
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 89 * hash + (this.faceBookNickname != null ? this.faceBookNickname.hashCode() : 0);
return hash;
}
Серьезно, хотя: что с NetBeans, не принимая во внимание суперклассы полей, вызывая методы суперкласса?
Ответ 2
Я предпочитаю использовать EqualsBuilder (и HashcodeBuilder) из commons-lang package, чтобы упростить чтение методов equals() и hashcode().
Пример:
public boolean equals(Object obj) {
if (obj == null) { return false; }
if (obj == this) { return true; }
if (obj.getClass() != getClass()) {
return false;
}
MyClass rhs = (MyClass) obj;
return new EqualsBuilder()
.appendSuper(super.equals(obj))
.append(field1, rhs.field1)
.append(field2, rhs.field2)
.append(field3, rhs.field3)
.isEquals();
}
Ответ 3
Вообще говоря, реализация equals в подклассах трудно сохранить симметрично и транзитивно.
Рассмотрим суперкласс, который проверяет поле x
и y
, а подкласс проверяет на x
, y
и z
.
Итак, подкласс == Суперкласс == Подкласс, где z отличается между первым экземпляром подкласса и вторым, нарушая транзитивную часть контракта.
Вот почему типичная реализация equals проверит getClass() != obj.getClass()
вместо того, чтобы делать instanceof. В приведенном выше примере, если SubClass или Superclass выполняет проверку экземпляра, это нарушит симметрию.
Итак, результат заключается в том, что подкласс, безусловно, может принимать во внимание super.equals(), но также должен выполнять свою собственную проверку getClass(), чтобы избежать вышеупомянутых проблем, а затем дополнительно проверить равные по своим полям. Это была бы странная утка класса, которая изменила бы свое собственное поведение равным образом на основе определенных полей суперкласса, а не только, если суперкласс возвращается равным.
Ответ 4
Правила:
- Это рефлексивно: для любого ненулевого опорного значения х, x.equals(х) должна возвращать истинное.
- Он симметричен: для любых непустых опорных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
- Это транзитивно: для любых непустых опорных значений x, y и z, если x.equals(y) возвращает true, а y.equals(z) возвращает true, тогда x.equals(z) должен возвращать true.
- Это согласовано: для любых непустых опорных значений x и y несколько вызовов x.equals(y) последовательно возвращают true или последовательно возвращают false, если информация, используемая при равных сравнениях с объектами, не изменяется.
- Для любого ненулевого опорного значения х, x.equals(NULL) должен возвращать ложь.
- Как правило, необходимо переопределять метод hashCode всякий раз, когда этот метод переопределяется, чтобы поддерживать общий контракт для метода hashCode, который утверждает, что равные объекты должны иметь одинаковые хэш-коды.
из Object.equals().
Итак, используйте поля, необходимые для выполнения правил.
Ответ 5
Поскольку наследование прерывает инкапсуляцию, подклассы, которые реализуют equals() и hashCode(), обязательно должны учитывать особенности своих суперклассов. У меня были успешные кодирования вызовов методам родительского класса equals() и hashCode() из методов подкласса.
Ответ 6
Что касается принятого ответа @CPerkins, я не думаю, что данный код equals() будет работать надежно, из-за вероятности того, что метод super.equals() также проверит равенство классов. Подкласс и суперкласс не будут иметь равных классов.
Ответ 7
Похоже, ваш родительский (супер) класс не переопределяет равные. Если это так, вам нужно сравнить поля из родительского класса, когда вы переопределяете этот метод в подклассе. Я согласен с тем, что использование общих ресурсов EqualsBuiler - это путь, но вам нужно быть осторожным, чтобы вы не нарушили симметрию/транзакционные части равного контракта.
Если ваш подкласс добавляет атрибуты родительскому классу, а родительский класс не является абстрактом и переопределяет, то вы столкнетесь с проблемой. В этом случае вы действительно должны смотреть на композицию объекта вместо наследования.
Я бы настоятельно рекомендовал этот раздел в "Эффективной Java" от Joshua Block. Это всеобъемлющий и очень хорошо объясненный.
Ответ 8
Ну, HomoSapiens#hashcode
будет достаточно с ответом CPerkins.
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 89 * hash + Objects.hash(name);
hash = 89 * hash + Objects.hash(faceBookNickname);
return hash;
}
Если вы хотите, чтобы эти родительские поля (gender
, weight
, height
) в действии, один из способов создает фактический экземпляр родительского типа и использует его. Слава Богу, это не абстрактный класс.
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final HomoSapiens other = (HomoSapiens) obj;
if (!super.equals(new Hominidae(
other.gender, other.weight, other.height))) {
return false;
}
if (!Objects.equals(name, other.name)) return false;
if (!Objects.equals(faceBookNickname, other.faceBookNickname))
return false;
return true;
}
Я добавляю способ (я думаю) решить это. Ключевым моментом является добавление метода, который свободно проверяет равенство.
public class Parent {
public Parent(final String name) {
super(); this.name = name;
}
@Override
public int hashCode() {
return hash = 53 * 7 + Objects.hashCode(name);
}
@Override
public boolean equals(final Object obj) {
return equalsAs(obj) && getClass() == obj.getClass();
}
protected boolean equalsAs(final Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Parent other = (Parent) obj;
if (!Objects.equals(name, other.name)) return false;
return true;
}
private final String name;
}
И вот идет Child
.
public class Child extends Parent {
public Child(final String name, final int age) {
super(name); this.age = age;
}
@Override
public int hashCode() {
return hash = 31 * super.hashCode() + age;
}
@Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
@Override
protected boolean equalsAs(final Object obj) {
if (!super.equalsAs(obj)) return false;
if (!getClass().isAssignableFrom(obj.getClass())) return false;
final Child other = (Child) obj;
if (age != other.age) return false;
return true;
}
private final int age;
}
Тестирование...
@Test(invocationCount = 128)
public void assertReflective() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
assertTrue(x.equals(x));
assertEquals(x.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertSymmetric() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(x));
assertEquals(y.hashCode(), x.hashCode());
}
@Test(invocationCount = 128)
public void assertTransitive() {
final String name = current().nextBoolean() ? "null" : null;
final int age = current().nextInt();
final Child x = new Child(name, age);
final Child y = new Child(name, age);
final Child z = new Child(name, age);
assertTrue(x.equals(y));
assertEquals(x.hashCode(), y.hashCode());
assertTrue(y.equals(z));
assertEquals(y.hashCode(), z.hashCode());
assertTrue(x.equals(z));
assertEquals(x.hashCode(), z.hashCode());
}
Ответ 9
Стоит отметить, что автогенерация IDE, возможно, учитывала суперкласс, только при условии, что equals() и hashCode() суперкласса еще существуют. То есть, следует автоматически генерировать эти две функции супер сначала, а затем автоматически генерировать ребенка. Я попал под правильный пример в Intellj Idea:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
TActivityWrapper that = (TActivityWrapper) o;
return data != null ? data.equals(that.data) : that.data == null;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (data != null ? data.hashCode() : 0);
return result;
}
Проблема возникает, когда вы не автоматически генерируете супер в первую очередь. Пожалуйста, проверьте выше в разделе Netbeans.