„Jetzt erzähle ich Ihnen einige Methoden, die genauso hilfreich sind:  equal(Object o) & hashCode() .“

„Sie haben sich wahrscheinlich schon daran erinnert, dass in Java beim Vergleich von Referenzvariablen nicht die Objekte selbst verglichen werden, sondern die Referenzen auf die Objekte.“

Code Erläuterung
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
i ist ungleich j.
Die Variablen zeigen auf verschiedene Objekte.
Auch wenn die Objekte die gleichen Daten enthalten.
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
i ist gleich j. Die Variablen enthalten einen Verweis auf dasselbe Objekt.

„Ja, daran erinnere ich mich.“

Die  Gleichen .

„Die Methode „equals“ ist hier die Standardlösung. Der Zweck der Methode „equals“ besteht darin, durch Vergleich dessen, was in ihnen gespeichert ist, festzustellen, ob Objekte intern identisch sind.“

„Und wie macht es das?“

„Es ist alles der toString()-Methode sehr ähnlich.“

Die Object-Klasse verfügt über eine eigene Implementierung der Methode equal, die einfach die Referenzen vergleicht:

public boolean equals(Object obj)
{
return (this == obj);
}

„Großartig... kommen wir noch einmal darauf zurück, oder?“

„Halten Sie den Kopf hoch! Es ist tatsächlich sehr schwierig.“

„Diese Methode wurde entwickelt, damit Entwickler sie in ihren eigenen Klassen überschreiben können. Schließlich weiß nur der Entwickler einer Klasse, welche Daten beim Vergleich relevant sind und welche nicht.“

„Können Sie ein Beispiel nennen?“

„Sicher. Angenommen, wir haben eine Klasse, die mathematische Brüche darstellt. Sie würde so aussehen:“

Beispiel:
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator  = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;

if (obj.getClass() != this.getClass() )
return false;

Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
Beispiel-Methodenaufruf:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
Der Methodenaufruf gibt true zurück.
Der Bruch 2/3 ist gleich dem Bruch 4/6

„Lassen Sie uns nun dieses Beispiel genauer untersuchen.“

„Wir haben die Methode „equals“ überschrieben , sodass Fraction- Objekte ihre eigene Implementierung haben.

„Die Methode enthält mehrere Prüfungen:“

1)  Wenn das zum Vergleich übergebene Objekt null ist , sind die Objekte nicht gleich. Wenn Sie die Methode equal für ein Objekt aufrufen können, ist es definitiv nicht null .“

2)  Ein Klassenvergleich. Wenn die Objekte Instanzen verschiedener Klassen sind, werden wir nicht versuchen, sie zu vergleichen. Stattdessen verwenden wir sofort return false , um anzugeben, dass es sich um unterschiedliche Objekte handelt.“

3)  Jeder erinnert sich aus der zweiten Klasse daran, dass 2/3 gleich 4/6 ist. Aber wie überprüft man das?“

2/3 == 4/6
Wir multiplizieren beide Seiten mit beiden Teilern (6 und 3) und erhalten:
6 * 2 == 4 * 3
12 == 12
Allgemeine Regel:
Wenn
a / b == c / d
Dann
a * d == c * b

„Dementsprechend wandeln wir im dritten Teil der equal- Methode das übergebene Objekt in einen Bruch um und vergleichen die Brüche.“

„Verstanden. Wenn wir einfach den Zähler mit dem Zähler und den Nenner mit dem Nenner vergleichen, dann ist 2/3 nicht gleich 4/6.“

„Jetzt verstehe ich, was Sie meinten, als Sie sagten, dass nur der Entwickler einer Klasse weiß, wie man sie richtig vergleicht.“

„Ja, aber das ist nur die halbe Wahrheit.  Es gibt noch eine andere Methode: hashCode().

„Alles an der Methode equal macht jetzt Sinn, aber warum brauchen wir  hashCode ()?

„Für schnelle Vergleiche wird die Methode hashCode benötigt.“

„Die Methode „equals“ hat einen großen Nachteil: Sie arbeitet zu langsam. Angenommen, Sie haben eine Menge von Millionen Elementen und müssen prüfen, ob sie ein bestimmtes Objekt enthält. Wie machen Sie das?“

„Ich könnte mithilfe einer Schleife alle Elemente durchlaufen und das Objekt mit jedem Objekt in der Menge vergleichen. Bis ich eine Übereinstimmung finde.“

„Und wenn es nicht da ist? Wir würden eine Million Vergleiche durchführen, nur um herauszufinden, dass das Objekt nicht da ist? Scheint das nicht viel zu sein?“

„Ja, selbst mir ist klar, dass das zu viele Vergleiche sind. Gibt es einen anderen Weg?“

„Ja, Sie können dafür hashCode () verwenden.

Die Methode hashCode () gibt für jedes Objekt eine bestimmte Zahl zurück. Der Entwickler einer Klasse entscheidet, welche Zahl zurückgegeben wird, genau wie er es bei der Methode equal tut.

„Sehen wir uns ein Beispiel an:“

„Stellen Sie sich vor, Sie haben eine Million 10-stellige Zahlen. Dann könnten Sie den HashCode jeder Zahl zum Rest machen, nachdem Sie die Zahl durch 100 geteilt haben.“

Hier ist ein Beispiel:

Nummer Unser HashCode
1234567890 90
9876554321 21
9876554221 21
9886554121 21

„Ja, das macht Sinn. Und was machen wir mit diesem HashCode?“

„Anstatt die Zahlen zu vergleichen, vergleichen wir ihre HashCodes . Das geht schneller.“

„Und wir rufen Equals nur auf, wenn ihre HashCodes gleich sind.“

„Ja, das ist schneller. Aber wir müssen immer noch eine Million Vergleiche durchführen. Wir vergleichen nur kleinere Zahlen und müssen immer noch „equals“ für alle Zahlen mit übereinstimmenden HashCodes aufrufen.“

„Nein, man kann mit einer viel geringeren Anzahl an Vergleichen auskommen.“

„Stellen Sie sich vor, dass unser Set Zahlen gruppiert oder nach HashCode sortiert speichert (sie auf diese Weise zu sortieren bedeutet im Wesentlichen, sie zu gruppieren, da Zahlen mit demselben HashCode nebeneinander liegen). Dann können Sie irrelevante Gruppen sehr schnell und einfach verwerfen. Das reicht.“ um einmal pro Gruppe zu prüfen, ob ihr HashCode mit dem HashCode des Objekts übereinstimmt.“

„Stellen Sie sich vor, Sie sind Student und suchen einen Freund, den Sie am Sehen erkennen können und von dem wir wissen, dass er im Wohnheim 17 wohnt. Dann gehen Sie einfach in jedes Wohnheim der Universität und fragen: ‚Ist das Wohnheim 17?‘ Wenn nicht, ignorierst du jeden im Wohnheim und gehst zum nächsten. Wenn die Antwort „Ja“ ist, dann gehst du an jedem Zimmer vorbei und suchst nach deinem Freund.“

„In diesem Beispiel ist die Wohnheimnummer (17) der HashCode.“

„Ein Entwickler, der eine HashCode-Funktion implementiert, muss Folgendes wissen:“

A)  Zwei verschiedene Objekte können denselben HashCode haben  (verschiedene Personen können im selben Wohnheim leben)

B)  Gleiche Objekte  ( gemäß der Methode equalmüssen denselben HashCode haben. .

C)  Hash-Codes müssen so gewählt werden, dass es nicht viele verschiedene Objekte mit demselben Hash-Code gibt.  Wenn dies der Fall ist, gehen die potenziellen Vorteile von Hashcodes verloren (Sie kommen zu Wohnheim 17 und stellen fest, dass dort die Hälfte der Universität wohnt. Schade!).

„Und jetzt das Wichtigste: Wenn Sie die Methode equals überschreiben, müssen Sie unbedingt die Methode hashCode () überschreiben und die drei oben beschriebenen Regeln einhalten.

„Der Grund ist folgender: In Java werden Objekte in einer Sammlung immer mit hashCode() verglichen/abgerufen, bevor sie mit equal verglichen/abgerufen werden.  Und wenn identische Objekte unterschiedliche hashCodes haben, dann werden die Objekte als unterschiedlich betrachtet und die Methode „equals“ verwendet wird nicht einmal angerufen.

„Wenn wir in unserem Fraktionsbeispiel den Hash-Code dem Zähler gleichsetzen würden, hätten die Brüche 2/3 und 4/6 unterschiedliche Hash-Codes. Die Brüche sind gleich und die Methode „equals“ sagt, dass sie gleich sind, aber ihre Hash-Codes sagen es Sie sind unterschiedlich. Und wenn wir mit hashCode vergleichen, bevor wir mit equal vergleichen, dann kommen wir zu dem Schluss, dass die Objekte unterschiedlich sind, und wir schaffen es nicht einmal bis zur Methode equal.“

Hier ist ein Beispiel:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
Wenn die Methode hashCode()  den Zähler der Brüche zurückgibt, ist das Ergebnis  false .
Und das Objekt „new Fraction(4,6) “ wird nicht in der Sammlung gefunden.

„Was ist also der richtige Weg, hashCode für Brüche zu implementieren?“

„Hier müssen Sie bedenken, dass äquivalente Brüche denselben HashCode haben müssen.“

Version 1 : Der HashCode entspricht dem Ergebnis der Ganzzahldivision.“

„Für 7/5 und 6/5 wäre dies 1.“

„Für 4/5 und 3/5 wäre das 0.“

„Für den Vergleich von Brüchen, die bewusst kleiner als 1 sind, eignet sich diese Option jedoch schlecht. Der HashCode (Ergebnis der Ganzzahldivision) ist immer 0.“

Version 2 : Der HashCode entspricht dem Ergebnis der ganzzahligen Division des Nenners durch den Zähler.“

„Diese Option eignet sich für Fälle, in denen der Bruch kleiner als 1 ist. Wenn der Bruch kleiner als 1 ist, ist sein Kehrwert größer als 1. Und wenn wir alle Brüche invertieren, werden die Vergleiche in keiner Weise beeinträchtigt.“

„Unsere finale Version vereint beide Lösungen:“

public int hashCode()
{
return numerator/denominator + denominator/numerator;
}

Testen wir es mit 2/3 und 4/6. Sie sollten identische HashCodes haben:

Fraktion 2/3 Fraktion 4/6
Zähler Nenner 2 / 3 == 0 4 / 6 == 0
Nenner / Zähler 3 / 2 == 1 6 / 4 == 1
Zähler/Nenner
+
Nenner/Zähler
0 + 1 == 1 0 + 1 == 1

„Das ist alles für den Moment.“

„Danke, Ellie. Das war wirklich interessant.“