web-dev-qa-db-fra.com

Remplacer equals () & hashCode () dans les sous-classes ... en considérant les super-champs

Existe-t-il une règle spécifique sur la manière dont Remplacer equals() & hashCode() dans sous-classes considérant super-champs ?? sachant qu'il y a beaucoup de paramètres: les super-champs sont privés/publics, avec/sans getter ...

Par exemple, Netbeans généré equals () et hashCode () ne prendront pas en compte les super-champs ... et

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

retournera vrai :(

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

Si vous voulez voir les Netbeans générés, 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;
    }
}
55
wj.

Les enfants ne doivent pas interroger les membres privés de leurs parents

Mais évidemment , tous les champs significatifs doivent être pris en compte pour l’égalité et le hachage.

Heureusement, vous pouvez facilement satisfaire les deux règles.

En supposant que vous n'êtes pas bloqué à l'aide de l'égalité et du hashcode générés par NetBeans, vous pouvez modifier la méthode equals de Hominidae pour qu'elle utilise instance de comparaison plutôt que l'égalité de classe, puis l'utiliser directement. Quelque chose comme ça:


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

Bien sûr, le hashcode est simple:


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

Sérieusement, que se passe-t-il avec NetBeans qui ne prend pas en compte les champs de la super-classe en appelant les méthodes de la super-classe?

56
CPerkins

Je préfère utiliser EqualsBuilder (et HashcodeBuilder) du paquet commons-lang pour faciliter la lecture de mes méthodes equals () et hashcode ().

Exemple:

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();
}
20
matt b

De manière générale, il est difficile de garder symétrique et transitive l'implémentation d'égaux dans toutes les sous-classes.

Prenons une superclasse qui vérifie les champs x et y, et les contrôles de sous-classes pour x, y et z.

Donc, une sous-classe == superclasse == sous-classe où z est différent entre la première instance de la sous-classe et la seconde, violant ainsi la partie transitive du contrat.

C'est pourquoi l'implémentation typique d'égaux recherchera getClass() != obj.getClass() au lieu de faire une instance de. Dans l'exemple ci-dessus, si SubClass ou Superclass effectue une vérification, cela romprait la symétrie.

Le résultat est qu’une sous-classe peut certainement prendre en compte super.equals () mais doit également effectuer sa propre vérification getClass () pour éviter les problèmes ci-dessus, puis rechercher des valeurs égales sur ses propres champs. Ce serait un canard étrange d’une classe qui a changé son propre comportement d’égaux en fonction de champs spécifiques de la superclasse plutôt que de simplement si la superclasse renvoie des égaux.

8
Yishai

Les règles sont:

  • C'est réflexif: pour toute valeur de référence non nulle x, x.equals (x) doit renvoyer true.
  • Il est symétrique: pour toute valeur de référence non nulle x et y, x.equals (y) doit renvoyer true si et seulement si y.equals (x) renvoie true.
  • Il est transitif: pour toute valeur de référence non nulle x, y et z, si x.equals (y) renvoie true et y.equals (z) renvoie true, alors x.equals (z) doit renvoyer true.
  • Il est cohérent: pour toute valeur de référence non nulle x et y, plusieurs appels de x.equals (y) retournent systématiquement true ou systématiquement false, à condition qu'aucune des informations utilisées dans les comparaisons égales des objets ne soit modifiée.
  • Pour toute valeur de référence non nulle x, x.equals (null) doit renvoyer false.
  • Il est généralement nécessaire de remplacer la méthode hashCode chaque fois que cette méthode est remplacée, de manière à conserver le contrat général pour la méthode hashCode, qui stipule que les objets identiques doivent avoir les mêmes codes de hachage.

de Object.equals ()

Alors, utilisez les champs nécessaires pour respecter les règles.

5
rodrigoap

Étant donné que l'héritage rompt l'encapsulation, les sous-classes implémentant equals () et hashCode () doivent nécessairement prendre en compte les particularités de leurs superclasses. J'ai eu du succès à coder des appels aux méthodes equals () et hashCode () de la classe parente à partir des méthodes de la sous-classe.

2
Steve Emmerson

En ce qui concerne la réponse acceptée de @CPerkins, je ne pense pas que le code equals () donné fonctionnera de manière fiable, car il est probable que la méthode super.equals () vérifie également l’égalité de classe. Une sous-classe et une super-classe n'auront pas des classes égales.

2
MattRing

HomoSapiens#hashcode suffira avec la réponse de CPerkins.

@Override     
public int hashCode() {     
    int hash = super.hashCode();
    hash = 89 * hash + Objects.hash(name);     
    hash = 89 * hash + Objects.hash(faceBookNickname);     
    return hash;     
}

Si vous souhaitez que les champs de ces parents (gender, weight, height) soient en action, vous pouvez créer une instance réelle de type parent et l'utiliser. Dieu merci, ce n'est pas un cours abstrait.

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

J'ajoute un moyen de (je pense) résoudre ceci. Le point clé est l’ajout d’une méthode qui vérifie l’égalité de façon approximative.

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

Et voici la 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;
}

Essai...

@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
Jin Kwon

Il semble que votre (super) classe parent ne remplace pas l'égalité. Si tel est le cas, vous devez comparer les champs de la classe parente lorsque vous substituez cette méthode à la sous-classe. Je suis d’accord pour dire que l’utilisation de la commune EqualsBuiler est la voie à suivre, mais vous devez faire attention à ne pas casser les parties symétrie/transative du contrat d’égalité.

Si votre sous-classe ajoute des attributs à la classe parente et si la classe parente n'est pas abstraite et remplace égale par égalité, vous allez avoir des ennuis. Dans ce scénario, vous devriez vraiment regarder la composition d'objet plutôt que l'héritage.

Je recommanderais fortement la section de Effective Java de Joshua Block à ce sujet. C'est complet et vraiment bien expliqué.

1
BigMikeW

Je crois qu'ils ont maintenant une méthode qui ne fait que cela pour vous:

EqualsBuilder.reflectionEquals (this, o);

HashCodeBuilder.reflectionHashCode (this);

0
Terry H

Il est à noter que la génération automatique IDE a peut-être pris en compte la super classe ,, à condition que les équivalents () et hashCode () de la super classe existent encore. C'est-à-dire, devrait générer automatiquement ces deux fonctions de super en premier, puis générer automatiquement de l'enfant. J'ai eu ci-dessous le bon exemple sous 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;
}

Le problème survient juste au moment où vous ne générez pas automatiquement de super en premier. Veuillez vérifier ci-dessus sous Netbeans.

0
Ming