Génère equals () et hashCode () avec Eclipse

1. Introduction

Dans cet article, nous explorons la génération de méthodes equals () et hashCode () à l’aide de l’EDI Eclipse. Nous allons illustrer à quel point la génération automatique de code Eclipse est puissante et pratique, et également souligner la nécessité de tester avec diligence le code.

2. Règles

equals () en Java est utilisé pour vérifier si 2 objets sont équivalents. Un bon moyen de vérifier cela est de s’assurer que les objets sont symétriques, réflexifs et transitifs. C’est-à-dire que pour trois objets non nuls a , b et c :

  • Symétrique - a.equals (b) si et seulement si b.equals (a)

  • Réflexif - a.équaux (a)

  • Transitive - si a.equals (b) et b.equals (c) alors a.equals (c)

hashCode () doit obéir à une règle:

  • 2 objets qui sont equals () doivent avoir le même hashCode () valeur

3. Classe avec primitives

Considérons une classe Java composée uniquement de variables membres primitives:

public class PrimitiveClass {

    private boolean primitiveBoolean;
    private int primitiveInt;

   //constructor, getters and setters
}

Nous utilisons l’EDI Eclipse pour générer equals () et hashCode () à l’aide de ‘Source→ Generate hashCode () et equals () ‘. Eclipse fournit une boîte de dialogue comme celle-ci:

Nous pouvons nous assurer que toutes les variables membres sont incluses en choisissant "Tout sélectionner".

Notez que les options listées sous Point d’insertion: affectent le style du code généré. Ici, nous ne sélectionnons aucune de ces options, sélectionnez "OK" et les méthodes sont ajoutées à notre classe:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime **  result + (primitiveBoolean ? 1231 : 1237);
    result = prime **  result + primitiveInt;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    PrimitiveClass other = (PrimitiveClass) obj;
    if (primitiveBoolean != other.primitiveBoolean) return false;
    if (primitiveInt != other.primitiveInt) return false;
    return true;
}

La méthode hashCode () générée commence par la déclaration d’un nombre premier (31), effectue diverses opérations sur les objets primitifs et renvoie son résultat en fonction de l’état de l’objet.

equals () vérifie d’abord si deux objets sont la même instance (==) et renvoie true s’ils le sont.

Ensuite, il vérifie que l’objet de comparaison est non nul et que les deux objets appartiennent à la même classe et renvoient false s’ils ne le sont pas.

Enfin, equals () vérifie l’égalité de chaque variable membre, en renvoyant la valeur false si l’une d’entre elles n’est pas égale.

Nous pouvons donc écrire des tests simples:

PrimitiveClass aObject = new PrimitiveClass(false, 2);
PrimitiveClass bObject = new PrimitiveClass(false, 2);
PrimitiveClass dObject = new PrimitiveClass(true, 2);

assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

4. Classe avec collections et génériques

Considérons maintenant une classe Java plus complexe avec des collections et des génériques:

public class ComplexClass {

    private List<?> genericList;
    private Set<Integer> integerSet;

   //constructor, getters and setters
}

Encore une fois, nous utilisons Eclipse 'Source→ Generate hashCode () et equals ()' . Notez que hashCode () utilise instanceOf pour comparer les objets de classe, car nous avons sélectionné 'Utiliser' instanceof 'pour comparer les types' dans les options Eclipse le dialogue. On a:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime **  result + ((genericList == null)
      ? 0 : genericList.hashCode());
    result = prime **  result + ((integerSet == null)
      ? 0 : integerSet.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (!(obj instanceof ComplexClass)) return false;
    ComplexClass other = (ComplexClass) obj;
    if (genericList == null) {
        if (other.genericList != null)
            return false;
    } else if (!genericList.equals(other.genericList))
        return false;
    if (integerSet == null) {
        if (other.integerSet != null)
            return false;
    } else if (!integerSet.equals(other.integerSet))
        return false;
    return true;
}

La méthode hashCode () générée repose sur les méthodes Java AbstractList.hashCode () et AbstractSet.hashCode () . Celles-ci parcourent une collection en faisant la somme des valeurs hashCode () de chaque élément et en renvoyant un résultat.

De même, la méthode equals () générée utilise AbstractList.equals () et AbstractSet.equals () , qui comparent les collections pour vérifier leur égalité en comparant leurs champs.

Nous pouvons vérifier la robustesse en testant quelques exemples:

ArrayList<String> strArrayList = new ArrayList<String>();
strArrayList.add("abc");
strArrayList.add("def");
ComplexClass aObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));
ComplexClass bObject = new ComplexClass(strArrayList, new HashSet<Integer>(45,67));

ArrayList<String> strArrayListD = new ArrayList<String>();
strArrayListD.add("lmn");
strArrayListD.add("pqr");
ComplexClass dObject = new ComplexClass(strArrayListD, new HashSet<Integer>(45,67));

assertTrue(aObject.equals(bObject) && bObject.equals(aObject));
assertTrue(aObject.hashCode() == bObject.hashCode());

assertFalse(aObject.equals(dObject));
assertFalse(aObject.hashCode() == dObject.hashCode());

5. Héritage

Considérons les classes Java qui utilisent l’héritage:

public abstract class Shape {
    public abstract double area();

    public abstract double perimeter();
}

public class Rectangle extends Shape {
    private double width;
    private double length;

    @Override
    public double area() {
        return width **  length;
    }

    @Override
    public double perimeter() {
        return 2 **  (width + length);
    }
   //constructor, getters and setters
}

public class Square extends Rectangle {
    Color color;
   //constructor, getters and setters
}

Si nous essayons le 'Source→ Generate hashCode () et equals () ' sur la classe Square , Eclipse nous avertit que la 'superclasse' Rectangle 'ne redéclare pas equals () et hashCode () : le code résultant peut ne pas fonctionner correctement '.

De même, nous recevons un avertissement concernant la super-classe ‘Shape’ lorsque nous essayons de générer hashCode () et equals () sur la classe Rectangle .

Eclipse nous permettra d’avancer malgré les avertissements. Dans le cas de Rectangle , il étend une classe abstraite Shape qui ne peut pas implémenter hashCode () ou equals () car elle ne contient aucune variable de membre concrète.

Nous pouvons ignorer Eclipse pour ce cas.

Cependant, la classe Square hérite des variables de membre width et length de Rectangle, ainsi que de sa propre variable de couleur. Créer hashCode () et equals () dans Square sans faire de même pour Rectangle signifie que vous n’utilisez que color dans equals () / hashCode () :

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime **  result + ((color == null) ? 0 : color.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null)
            return false;
    } else if (!color.equals(other.color))
        return false;
    return true;
}

Un test rapide nous montre que equals () / hashCode () pour Square ne sont pas suffisants si seul le width diffère, car width n’est pas inclus dans equals () / hashCode () calculs:

Square aObject = new Square(10, Color.BLUE);
Square dObject = new Square(20, Color.BLUE);

Assert.assertFalse(aObject.equals(dObject));
Assert.assertFalse(aObject.hashCode() == dObject.hashCode());

Corrigeons cela en utilisant Eclipse pour générer equals () / hashCode () pour la classe Rectangle :

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    long temp;
    temp = Double.doubleToLongBits(length);
    result = prime **  result + (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(width);
    result = prime **  result + (int) (temp ^ (temp >>> 32));
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    Rectangle other = (Rectangle) obj;
    if (Double.doubleToLongBits(length)
      != Double.doubleToLongBits(other.length)) return false;
    if (Double.doubleToLongBits(width)
      != Double.doubleToLongBits(other.width)) return false;
    return true;
}

Nous devons régénérer equals () / hashCode () dans la classe Square , de sorte que Rectangle ’s equals () / hashCode () soit appelé. Dans cette génération de code, nous avons sélectionné toutes les options de la boîte de dialogue Eclipse. Nous voyons donc les commentaires, les comparaisons instanceOf et les blocs if :

@Override
public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime **  result + ((color == null) ? 0 : color.hashCode());
    return result;
}


@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!super.equals(obj)) {
        return false;
    }
    if (!(obj instanceof Square)) {
        return false;
    }
    Square other = (Square) obj;
    if (color == null) {
        if (other.color != null) {
            return false;
       }
    } else if (!color.equals(other.color)) {
        return false;
    }
    return true;
}

En relançant notre test à partir d’en haut, nous passons maintenant parce que Square ’s hashCode () / est égal à () sont calculés correctement.

6. Conclusion

L’Eclipse IDE est très puissant et permet la génération automatique d’un code passe-partout - des getters/setters, des constructeurs de différents types, equals () et hashCode () .

En comprenant ce que fait Eclipse, nous pouvons réduire le temps consacré à ces tâches de codage. Cependant, nous devons toujours faire preuve de prudence et vérifier notre code avec des tests pour nous assurer que nous avons traité tous les cas attendus.

Les extraits de code, comme toujours, peuvent être trouvés à l’adresse https://github.com/eugenp/tutorials/tree/master/core-java-lang [over sur GitHub.