Переопределение equals () и hashCode () в подклассах ... с учетом суперполей - PullRequest
56 голосов
/ 14 января 2010

Существует ли конкретное правило о том, как переопределять equals() & hashCode() в подклассах с учетом суперполей ?? зная, что есть много параметров: супер-поля являются приватными / открытыми, с / без геттера ...

Например, сгенерированные NetBeans equals () и hashCode () не будут учитывать суперполя ... и

    new HomoSapiens("M", "80", "1.80", "Cammeron", "VeryHot").equals(
    new HomoSapiens("F", "50", "1.50", "Cammeron", "VeryHot"))

вернет истину: (

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;
    }
}

Ответы [ 10 ]

56 голосов
/ 14 января 2010

Дети не должны осматривать частных членов своих родителей

Но очевидно , все значимые поля должны быть приняты во внимание для равенства и хеширования.

К счастью, вы легко можете удовлетворить оба правила.

Предполагая, что вы не застряли, используя сгенерированные NetBeans функции equals и hashcode, вы можете изменить метод equals Hominidae, чтобы использовать экземпляр сравнения, а не равенство классов, а затем использовать его напрямую. Примерно так:


    @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
        }

Конечно, хеш-код прост:


    @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, который не учитывает поля суперкласса, вызывая методы суперкласса?

20 голосов
/ 14 января 2010

Я предпочитаю использовать EqualsBuilder (и HashcodeBuilder) из пакета commons-lang , чтобы облегчить чтение моих методов 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();
}
8 голосов
/ 14 января 2010

Вообще говоря, реализацию равных в подклассах трудно сохранить симметричной и транзитивной.

Рассмотрим суперкласс, который проверяет поля x и y, и проверки подкласса для x, y и z.

Итак, Подкласс == Суперкласс == Подкласс, где z отличается от первого экземпляра Подкласса до второго, нарушая переходную часть контракта.

Вот почему типичная реализация equals будет проверять getClass() != obj.getClass() вместо создания instanceof. В приведенном выше примере, если SubClass или Superclass выполнит экземпляр проверки, это нарушит симметрию.

Таким образом, в результате подкласс может, конечно, учитывать super.equals (), но также должен выполнять собственную проверку getClass (), чтобы избежать вышеуказанных проблем, а затем дополнительно проверять наличие равных в своих собственных полях. Это была бы странная утка класса, который изменил свое собственное поведение equals на основе определенных полей суперкласса, а не просто, если суперкласс возвращает равно.

5 голосов
/ 14 января 2010

Правила:

  • Это рефлексивно: для любого ненулевого ссылочного значения x, x.equals (x) должно возвращать true.
  • Это симметрично: для любых ненулевых ссылочных значений 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) последовательно возвращают истину или последовательно возвращают ложь, при условии, что никакая информация, используемая в сравнениях сравнения на объектах, не изменяется.
  • Для любого ненулевого ссылочного значения x, x.equals (null) должно возвращать false.
  • Обычно необходимо переопределять метод hashCode всякий раз, когда этот метод переопределяется, чтобы поддерживать общий контракт для метода hashCode, в котором говорится, что равные объекты должны иметь одинаковые хеш-коды

из Object.equals () .

Итак, используйте поля, необходимые для выполнения правил.

2 голосов
/ 10 мая 2013

Что касается принятого ответа @CPerkins, я не думаю, что данный код equals () будет работать надежно из-за вероятности, что метод super.equals () также проверит на равенство классов. У подкласса и суперкласса не будет одинаковых классов.

2 голосов
/ 14 января 2010

Поскольку наследование нарушает инкапсуляцию, подклассы, которые реализуют equals () и hashCode (), должны обязательно учитывать особенности своих суперклассов. Я успешно кодировал вызовы методов equals () и hashCode () родительского класса из методов подкласса.

1 голос
/ 08 сентября 2016

Стоит отметить, что автогенерация 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.

1 голос
/ 20 августа 2016

Ну, 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());
}
1 голос
/ 15 января 2010

Похоже, ваш родительский (супер) класс не переопределяет "равно". Если это так, то вам нужно сравнить поля из родительского класса, когда вы переопределяете этот метод в подклассе. Я согласен с тем, что использовать обыкновенные EqualsBuiler - это путь, но вам нужно быть осторожным, чтобы не нарушить симметрию / переходные части контракта equals.

Если ваш подкласс добавляет атрибуты к родительскому классу, а родительский класс не является абстрактным и переопределяет, то вы попадаете в неприятности. В этом сценарии вы должны смотреть на композицию объектов, а не на наследование.

Я бы настоятельно рекомендовал раздел «Эффективная Java» Джошуа Блока. Это всеобъемлющее и действительно хорошо объясненное.

0 голосов
/ 06 июня 2018

Полагаю, у них теперь есть метод, который делает это за вас:

EqualsBuilder.reflectionEquals (this, o);

HashCodeBuilder.reflectionHashCode (это);

...