Generiere equals () und hashCode () mit Eclipse

1. Einführung

In diesem Artikel wird das Generieren der Methoden equals () und hashCode () mithilfe der Eclipse-IDE untersucht. Wir zeigen Ihnen, wie leistungsfähig und praktisch die automatische Codegenerierung von Eclipse ist, und betonen auch, dass ein sorgfältiges Testen des Codes immer noch erforderlich ist.

2. Regeln

equals () in Java wird verwendet, um zu prüfen, ob 2 Objekte gleichwertig sind. Um dies zu testen, sollten Sie sicherstellen, dass Objekte symmetrisch, reflexiv und transitiv sind. Das heißt für drei Nicht-Null-Objekte a , b und c :

  • Symmetrisch - a.equals (b) wenn und nur wenn b.equals (a)

  • Reflexiv - a.equals (a)

  • Transitiv - wenn a.equals (b) und b.equals (c) dann a.equals (c)

hashCode () muss einer Regel folgen:

  • 2 Objekte mit equals () müssen denselben hashCode () -Wert haben

3. Klasse mit Primitiven

Betrachten wir eine Java-Klasse, die nur aus primitiven Member-Variablen besteht:

public class PrimitiveClass {

    private boolean primitiveBoolean;
    private int primitiveInt;

   //constructor, getters and setters
}

Wir verwenden die Eclipse-IDE zum Generieren von equals () und hashCode () mithilfe von 'Source→ Generate hashCode () und equals () '. Eclipse bietet ein Dialogfeld wie dieses:

Wir können sicherstellen, dass alle Elementvariablen enthalten sind, indem Sie Alle auswählen auswählen.

Beachten Sie, dass die unter Einfügepunkt: aufgelisteten Optionen den Stil des generierten Codes beeinflussen. Hier wählen wir keine dieser Optionen aus, wählen "OK" und die Methoden werden unserer Klasse hinzugefügt:

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

Die generierte Methode hashCode () beginnt mit der Deklaration einer Primzahl (31), führt verschiedene Operationen an primitiven Objekten durch und gibt ihr Ergebnis basierend auf dem Zustand des Objekts zurück.

equals () prüft zuerst, ob zwei Objekte dieselbe Instanz (==) sind, und gibt true zurück, wenn sie vorhanden sind.

Anschließend wird geprüft, ob das Vergleichsobjekt nicht null ist und beide Objekte derselben Klasse angehören. Wenn nicht, wird false zurückgegeben.

Schließlich überprüft equals () die Gleichheit jeder Elementvariablen und gibt false zurück, wenn eine von ihnen nicht gleich ist.

So können wir einfache Tests schreiben:

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. Klasse mit Sammlungen und Generika

Betrachten wir nun eine komplexere Java-Klasse mit Sammlungen und Generics:

public class ComplexClass {

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

   //constructor, getters and setters
}

Wieder verwenden wir Eclipse 'Source→ Generate hashCode () und equals ()' . Beachten Sie, dass hashCode () instanceOf zum Vergleichen von Klassenobjekten verwendet, da in den Eclipse-Optionen 'instanceOf' verwendet wird der Dialog. Wir bekommen:

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

Die generierte Methode hashCode () basiert auf den Java-Kernmethoden AbstractList.hashCode () und AbstractSet.hashCode () . Diese durchlaufen eine Auflistung, summieren hashCode () - Werte jedes Elements und geben ein Ergebnis zurück.

In ähnlicher Weise verwendet die generierte equals () - Methode AbstractList.equals () und AbstractSet.equals () , die Sammlungen auf Gleichheit vergleichen, indem sie ihre Felder vergleichen.

Wir können die Robustheit anhand einiger Beispiele überprüfen:

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. Erbe

Betrachten wir Java-Klassen, die Vererbung verwenden:

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
}

Wenn wir versuchen, 'Source→ Generate hashCode () und equals () ' für die Square -Klasse zu verwenden, warnt uns Eclipse, dass 'die Oberklasse' Rectangle ' equals () und hashCode () nicht neu deklariert funktioniert möglicherweise nicht richtig '.

Auf ähnliche Weise erhalten wir eine Warnung bezüglich der Oberklasse "Shape", wenn wir versuchen, hashCode () und equals () in der Rectangle -Klasse zu generieren.

Eclipse wird es uns ermöglichen, trotz Warnungen vorwärts zu pflügen. Im Fall von Rectangle erweitert es eine abstrakte Shape -Klasse, die hashCode () oder equals () nicht implementieren kann, da keine konkreten Member-Variablen vorhanden sind.

Wir können Eclipse für diesen Fall ignorieren.

Die Square -Klasse erbt jedoch die Member-Variablen width und length von Rectangle sowie die eigene Farbvariable. Das Erstellen von hashCode () und equals () in Square ohne vorher das gleiche für Rectangle auszuführen, bedeutet, dass nur color in equals () / hashCode () verwendet wird:

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

Ein Schnelltest zeigt uns, dass equals () / hashCode () für Square nicht ausreicht, wenn sich nur die width unterscheidet, da width nicht in equals () / hashCode () -Berechnungen enthalten ist:

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());

Beheben Sie dies, indem Sie mit Eclipse equals () / hashCode () für die Rectangle -Klasse generieren:

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

Wir müssen equals () / hashCode () in der Square -Klasse erneut generieren, sodass Rectangle s equals () / hashCode () aufgerufen wird. In dieser Codegeneration haben wir alle Optionen im Eclipse-Dialogfeld ausgewählt, sodass Kommentare, instanceOf -Vergleiche und if -Blöcke angezeigt werden:

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

Wir führen unseren Test erneut von oben aus und bestehen jetzt, da Square ’s hashCode () / equals () korrekt berechnet werden.

6. Fazit

Die Eclipse-IDE ist sehr leistungsfähig und ermöglicht die automatische Erzeugung eines Boilerplate-Codes - Getter/Setter, Konstruktoren verschiedener Typen, equals () und hashCode () .

Wenn wir wissen, was Eclipse macht, können wir die für diese Codierungsaufgaben aufgewendete Zeit reduzieren. Trotzdem müssen wir Vorsicht walten lassen und unseren Code durch Tests überprüfen, um sicherzustellen, dass wir alle erwarteten Fälle bearbeitet haben.

Code-Snippets finden Sie wie immer auf GitHub unter https://github.com/eugenp/tutorials/tree/master/core-java-lang