hashCode implementieren
Korrekte Hash-Funktionen schreiben
hashCode implementieren ist eine kostenlose Java Academy-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Java Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Ziele eines guten hashCode
Ein guter hashCode() sollte:
- Für gleiche Objekte denselben Wert zurückgeben (der Vertrag).
- Ungleiche Objekte auf viele verschiedene Werte verteilen.
- Kostengünstig zu berechnen sein.
Ein schlechter hashCode, der eine Konstante zurückgibt, erfüllt zwar den Vertrag, verwandelt die Map aber in eine langsame verkettete Liste.
public class Main {
public static void main(String[] args) {
// Legal but terrible: every object collides
System.out.println("constant hashCode is legal but kills performance");
}
}Objects.hash für den häufigsten Fall
Der einfachste korrekte Ansatz ist Objects.hash(field1, field2, ...).
Die Methode behandelt null-Werte und kombiniert Felder mit einem Standardalgorithmus. Verwenden Sie dieselben Felder, die Sie in equals vergleichen.
import java.util.Objects;
public class Main {
static class User {
final String name; final int age;
User(String name, int age) { this.name = name; this.age = age; }
@Override public int hashCode() { return Objects.hash(name, age); }
}
public static void main(String[] args) {
User a = new User("Ada", 36);
User b = new User("Ada", 36);
System.out.println(a.hashCode() == b.hashCode());
}
}Der klassische 31-Multiplikator
Bei einem von Hand geschriebenen Hash wird das laufende Ergebnis üblicherweise mit 31 multipliziert und der Hash jedes Felds addiert.
31 ist eine ungerade Primzahl, und 31 * x entspricht (x << 5) - x, sodass die JVM dies optimieren kann.
public class Main {
static class User {
final String name; final int age;
User(String name, int age) { this.name = name; this.age = age; }
@Override public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + age;
return result;
}
}
public static void main(String[] args) {
System.out.println(new User("Ada", 36).hashCode());
}
}Primitive Typen hashen
Für jeden primitiven Typ gibt es eine empfohlene Methode zum Hashen:
int: Verwenden Sie den Wert selbst.long:(int)(value ^ (value >>> 32)).boolean: 1 oder 0.double:Double.hashCode(value).
public class Main {
public static void main(String[] args) {
long id = 4_000_000_000L;
int longHash = (int) (id ^ (id >>> 32));
System.out.println("long hash: " + longHash);
System.out.println("double hash: " + Double.hashCode(3.14));
System.out.println("bool hash: " + Boolean.hashCode(true));
}
}Arrays hashen
Rufen Sie hashCode() nicht direkt für ein Array auf; dabei wird die Identität und nicht der Inhalt verwendet.
Verwenden Sie Arrays.hashCode(arr) für ein einfaches Array oder Arrays.deepHashCode(arr) für verschachtelte Arrays.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println("identity equal: " + (a.hashCode() == b.hashCode()));
System.out.println("content equal: " + (Arrays.hashCode(a) == Arrays.hashCode(b)));
}
}equals und hashCode synchron halten
Die in hashCode() verwendeten Felder müssen eine Teilmenge der in equals() verwendeten Felder sein (idealerweise genau dieselben).
Wenn equals mehr Felder vergleicht als hashCode, haben zwei gleiche Objekte trotzdem denselben Hash. Das ist zulässig. Wenn hashCode jedoch ein Feld verwendet, das equals ignoriert, verletzen Sie den Vertrag.
import java.util.Objects;
public class Main {
static class Coord {
final int x, y;
Coord(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
return o instanceof Coord c && c.x == x && c.y == y;
}
@Override public int hashCode() { return Objects.hash(x, y); }
}
public static void main(String[] args) {
Coord a = new Coord(3, 4), b = new Coord(3, 4);
System.out.println(a.equals(b) && a.hashCode() == b.hashCode());
}
}Den Hash zwischenspeichern
Bei unveränderlichen Objekten mit aufwendiger Hashberechnung können Sie das Ergebnis in einem Feld zwischenspeichern.
String macht dies intern genau so. Tun Sie das nur, wenn das Objekt wirklich unveränderlich ist, damit der zwischengespeicherte Wert niemals veraltet.
import java.util.Objects;
public class Main {
static final class Key {
final String a, b;
private int hash; // 0 until computed
Key(String a, String b) { this.a = a; this.b = b; }
@Override public int hashCode() {
int h = hash;
if (h == 0) { h = Objects.hash(a, b); hash = h; }
return h;
}
}
public static void main(String[] args) {
Key k = new Key("x", "y");
System.out.println(k.hashCode());
System.out.println(k.hashCode());
}
}Die Verteilung ist wichtig
Ein gut verteilter hashCode verteilt Schlüssel gleichmäßig auf die Buckets. Zählen wir die unterschiedlichen Hashcodes für eine Gruppe von Objekten.
Je mehr unterschiedliche Werte es gibt, desto weniger Kollisionen treten auf und desto schneller ist die Map.
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
record Pair(int a, int b) {}
public static void main(String[] args) {
Set<Integer> hashes = new HashSet<>();
for (int i = 0; i < 100; i++) {
hashes.add(Objects.hash(i, i * 7));
}
System.out.println("distinct hashes: " + hashes.size());
}
}Beispiel für eine schlechte Verteilung
Das Addieren von Feldern ohne Multiplikation erzeugt Kollisionen: (1,2) und (2,1) ergeben beide den Hash 3.
Der 31-Multiplikator beseitigt diese Symmetrie, weil dadurch die Reihenfolge berücksichtigt wird.
public class Main {
static int badHash(int a, int b) { return a + b; }
static int goodHash(int a, int b) { return 31 * a + b; }
public static void main(String[] args) {
System.out.println("bad (1,2): " + badHash(1, 2) + ", (2,1): " + badHash(2, 1));
System.out.println("good (1,2): " + goodHash(1, 2) + ", (2,1): " + goodHash(2, 1));
}
}Records für Werttypen bevorzugen
Für reine Datenträger erzeugt ein record automatisch einen korrekten und gut verteilten hashCode.
Schreiben Sie hashCode nur dann von Hand, wenn Sie eine eigene Semantik benötigen oder keinen Record verwenden können.
public class Main {
record Money(long cents, String currency) {}
public static void main(String[] args) {
Money a = new Money(1099, "USD");
Money b = new Money(1099, "USD");
System.out.println(a.equals(b));
System.out.println(a.hashCode() == b.hashCode());
}
}Alles zusammenführen
Eine vollständige Wertklasse: unveränderliche Felder, equals und hashCode aus denselben Feldern sowie ein sauberes toString.
import java.util.Objects;
public class Main {
static final class Version {
final int major, minor, patch;
Version(int major, int minor, int patch) {
this.major = major; this.minor = minor; this.patch = patch;
}
@Override public boolean equals(Object o) {
return o instanceof Version v && v.major == major && v.minor == minor && v.patch == patch;
}
@Override public int hashCode() { return Objects.hash(major, minor, patch); }
@Override public String toString() { return major + "." + minor + "." + patch; }
}
public static void main(String[] args) {
Version v = new Version(2, 1, 0);
System.out.println(v + " hash=" + v.hashCode());
}
}Kurze Überprüfung
Testen Sie Ihre hashCode-Kenntnisse.
Zusammenfassung
Sie haben gelernt, hashCode korrekt zu implementieren:
- Verwenden Sie
Objects.hash(...)für den häufigsten Fall. - Verwenden Sie das Muster mit dem 31-Multiplikator für Hashes, die Sie von Hand schreiben.
- Hashen Sie Arrays mit
Arrays.hashCode, nicht mit der Standardimplementierung. - Halten Sie die für hashCode verwendeten Felder mit equals synchron und bevorzugen Sie Records.
Als Nächstes sehen Sie, wie Java 8+ überfüllte Buckets in Bäume umwandelt.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("hashCode recap done: " + Objects.hash("done"));
}
}Häufig gestellte Fragen
Ist die Lektion „hashCode implementieren“ kostenlos?
Ja — der vollständige Text von „hashCode implementieren“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Java Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Java Academy-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „hashCode implementieren“?
Korrekte Hash-Funktionen schreiben Du übst Java Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um Java Academy zu starten?
Keine Vorkenntnisse erforderlich. Java Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.
Wie lange dauert die Lektion „hashCode implementieren“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser Java Academy-Lektion Code schreiben und ausführen?
Ja. Jede Java Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- Funktionsweise von HashMap
- Der equals/hashCode-Vertrag
- hashCode implementieren
- Treeification und Performance