Java equals() und hashCode()

Java equals() und hashCode() Methoden sind in der Object-Klasse vorhanden. Daher erhält jede Java-Klasse die Standardimplementierung von equals() und hashCode(). In diesem Beitrag werden wir uns die Java equals() und hashCode() Methoden im Detail ansehen.

Java equals()

Die equals()-Methode in der Object-Klasse ist wie folgt definiert:

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

Gemäß der Java-Dokumentation für die equals()-Methode sollte jede Implementierung den folgenden Grundsätzen entsprechen.

  • Für jedes Objekt x sollte x.equals(x) true zurückgeben.
  • Für zwei Objekte x und y sollte x.equals(y) true zurückgeben, nur wenn y.equals(x) true zurückgibt.
  • Für mehrere Objekte x, y und z gilt: Wenn x.equals(y) true zurückgibt und y.equals(z) true zurückgibt, sollte auch x.equals(z) true zurückgeben.
  • Mehrere Aufrufe von x.equals(y) sollten dasselbe Ergebnis zurückgeben, es sei denn, eine der Objekteigenschaften, die in der equals()-Methodenimplementierung verwendet wird, wurde geändert.
  • Die Implementierung der equals()-Methode in der Object-Klasse gibt nur dann true zurück, wenn beide Verweise auf dasselbe Objekt zeigen.

Java hashCode()

Die Java Object hashCode() Methode ist eine native Methode und gibt den ganzzahligen Hash-Code-Wert des Objekts zurück. Der allgemeine Vertrag der hashCode() Methode lautet:

  • Mehrere Aufrufe von hashCode() sollten denselben ganzzahligen Wert zurückgeben, es sei denn, die Objekteigenschaft, die in der equals() Methode verwendet wird, wird geändert.
  • Der Hash-Code-Wert eines Objekts kann sich bei mehreren Ausführungen derselben Anwendung ändern.
  • Wenn zwei Objekte gemäß der equals() Methode gleich sind, muss ihr Hash-Code gleich sein.
  • Wenn zwei Objekte gemäß der equals() Methode ungleich sind, müssen ihre Hash-Codes nicht unterschiedlich sein. Ihr Hash-Code-Wert kann gleich oder ungleich sein.

Wichtigkeit der equals() und hashCode() Methode

Die Java hashCode() und equals() Methode werden in hash-tabellenbasierten Implementierungen in Java zum Speichern und Abrufen von Daten verwendet. Ich habe es ausführlich in Wie funktioniert HashMap in Java? erklärt. Die Implementierung von equals() und hashCode() sollte diese Regeln befolgen.

  • Wenn o1.equals(o2) gilt, dann sollte o1.hashCode() == o2.hashCode() immer true sein.
  • Wenn o1.hashCode() == o2.hashCode wahr ist, bedeutet das nicht, dass o1.equals(o2) wahr sein wird.

Wann sollte man die Methoden equals() und hashCode() überschreiben?

Wenn wir die equals() Methode überschreiben, ist es fast notwendig, auch die hashCode() Methode zu überschreiben, damit ihr Vertrag nicht durch unsere Implementierung verletzt wird. Beachten Sie, dass Ihr Programm keine Ausnahmen auslöst, wenn der Vertrag von equals() und hashCode() verletzt wird. Wenn Sie nicht beabsichtigen, die Klasse als Schlüssel für die Hashtabelle zu verwenden, wird dies kein Problem darstellen. Wenn Sie jedoch eine Klasse als Schlüssel für die Hashtabelle verwenden möchten, ist es erforderlich, sowohl die equals() als auch die hashCode() Methoden zu überschreiben. Schauen wir uns an, was passiert, wenn wir uns auf die Standardimplementierung der equals() und hashCode() Methoden verlassen und eine benutzerdefinierte Klasse als HashMap-Schlüssel verwenden.

package com.journaldev.java;

public class DataKey {

	private String name;
	private int id;

        // Getter- und Setter-Methoden

	@Override
	public String toString() {
		return "DataKey [name=" + name + ", id=" + id + "]";
	}

}
package com.journaldev.java;

import java.util.HashMap;
import java.util.Map;

public class HashingTest {

	public static void main(String[] args) {
		Map<DataKey, Integer> hm = getAllData();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		Integer value = hm.get(dk);

		System.out.println(value);

	}

	private static Map<DataKey, Integer> getAllData() {
		Map<DataKey, Integer> hm = new HashMap<>();

		DataKey dk = new DataKey();
		dk.setId(1);
		dk.setName("Pankaj");
		System.out.println(dk.hashCode());

		hm.put(dk, 10);

		return hm;
	}

}

Wenn wir das oben genannte Programm ausführen, wird es null drucken. Das liegt daran, dass die Object hashCode() Methode verwendet wird, um den Eimer zu finden, in dem nach dem Schlüssel gesucht wird. Da wir keinen Zugriff auf die HashMap-Schlüssel haben und den Schlüssel erneut erstellen, um die Daten abzurufen, werden Sie feststellen, dass die Hash-Code-Werte beider Objekte unterschiedlich sind und daher der Wert nicht gefunden wird.

Implementierung der equals() und hashCode() Methode

Wir können unsere eigenen equals() und hashCode() Methodenimplementierungen definieren, aber wenn wir sie nicht sorgfältig implementieren, können seltsame Probleme zur Laufzeit auftreten. Glücklicherweise bieten die meisten IDEs heutzutage Möglichkeiten, sie automatisch zu implementieren, und falls erforderlich, können wir sie entsprechend unseren Anforderungen ändern. Wir können Eclipse verwenden, um equals() und hashCode() Methoden automatisch zu generieren. Hier sind die automatisch generierten Implementierungen der equals() und hashCode() Methoden.

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + id;
	result = prime * result + ((name == null) ? 0 : name.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;
	DataKey other = (DataKey) obj;
	if (id != other.id)
		return false;
	if (name == null) {
		if (other.name != null)
			return false;
	} else if (!name.equals(other.name))
		return false;
	return true;
}

Beachten Sie, dass sowohl die equals() als auch die hashCode() Methoden dieselben Felder für die Berechnungen verwenden, damit ihr Vertrag gültig bleibt. Wenn Sie das Testprogramm erneut ausführen, erhalten wir das Objekt aus der Map, und das Programm gibt 10 aus. Wir können auch Project Lombok verwenden, um equals() und hashCode() Methoden automatisch zu generieren.

Was ist eine Hash-Kollision

In sehr einfachen Worten verwenden Java-Hash-Tabellenimplementierungen folgende Logik für die get- und put-Operationen.

  1. Identifizieren Sie zunächst den „Eimer“, der mit dem „Schlüssel“ Hash-Code verwendet werden soll.
  2. Wenn im Eimer keine Objekte mit demselben Hash-Code vorhanden sind, fügen Sie das Objekt für die put-Operation hinzu und geben Sie für die get-Operation null zurück.
  3. Wenn es andere Objekte im Eimer mit dem gleichen Hash-Code gibt, kommt die „key“ equals-Methode ins Spiel.
    • Wenn equals() true zurückgibt und es sich um eine Put-Operation handelt, wird der Objektwert überschrieben.
    • Wenn equals() false zurückgibt und es sich um eine Put-Operation handelt, wird ein neuer Eintrag zum Eimer hinzugefügt.
    • Wenn equals() true zurückgibt und es sich um eine Get-Operation handelt, wird der Objektwert zurückgegeben.
    • Wenn equals() false zurückgibt und es sich um eine Get-Operation handelt, wird null zurückgegeben.

Das untenstehende Bild zeigt die Elemente eines Eimers in einer HashMap und wie ihre equals() und hashCode() zusammenhängen. Das Phänomen, wenn zwei Schlüssel denselben Hash-Code haben, wird als Hash-Kollision bezeichnet. Wenn die Methode hashCode() nicht ordnungsgemäß implementiert ist, kommt es zu einer höheren Anzahl von Hash-Kollisionen, und Einträge in der Map werden nicht ordnungsgemäß verteilt, was zu Verlangsamungen bei den Get- und Put-Operationen führt.

Was passiert, wenn wir weder hashCode() noch equals() implementieren?

Wir haben bereits oben gesehen, dass wenn hashCode() nicht implementiert ist, wir den Wert nicht abrufen können, da HashMap den Hashcode verwendet, um den Eimer zu finden, in dem der Eintrag gesucht wird. Wenn wir nur hashCode() verwenden und equals() nicht implementieren, wird der Wert ebenfalls nicht abgerufen, weil die equals()-Methode false zurückgeben wird.

Best Practices für die Implementierung der equals() und hashCode() Methode

  • Verwenden Sie dieselben Eigenschaften in der Implementierung sowohl von equals() als auch von hashCode(), damit ihr Vertrag nicht verletzt wird, wenn eine Eigenschaft aktualisiert wird.
  • Es ist besser, unveränderliche Objekte als Schlüssel für die Hashtabelle zu verwenden, damit wir den Hashcode zwischenspeichern können, anstatt ihn bei jedem Aufruf neu zu berechnen. Deshalb ist String ein guter Kandidat für den Schlüssel der Hashtabelle, weil er unveränderlich ist und den Hashcode-Wert zwischenspeichert.
  • Implementieren Sie die hashCode()-Methode, damit die geringste Anzahl von Hash-Kollisionen auftritt und die Einträge gleichmäßig auf alle Eimer verteilt sind.

Sie können den vollständigen Code von unserem GitHub-Repository herunterladen.

Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode