Java Academy · Les

hashCode implementeren

Correcte hashfuncties schrijven

Les 3 van 413 stappen

hashCode implementeren is een gratis Java Academy-les op CoddyKit. Dit is les 3 van 4. Je kunt de volledige les hieronder gratis lezen en daarna in de browser praktisch oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Java Academy. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Java Academy bevat in totaal 4 lessen.

Doelen van een goede hashCode

Een goede hashCode() moet:

  • Voor gelijke objecten dezelfde waarde teruggeven (het contract).
  • Ongelijke objecten over veel verschillende waarden verspreiden.
  • Goedkoop te berekenen zijn.

Een slechte hashCode die een constante teruggeeft, voldoet nog steeds aan het contract, maar verandert de map in een trage gekoppelde lijst.

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 voor het algemene geval

De eenvoudigste correcte aanpak is Objects.hash(field1, field2, ...).

De methode verwerkt nullwaarden en combineert velden met een standaardalgoritme. Gebruik dezelfde velden die je in equals vergelijkt.

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

De klassieke vermenigvuldigingsfactor 31

Bij een handgeschreven hash vermenigvuldigt het standaardpatroon een tussenresultaat met 31 en telt het de hash van elk veld erbij op.

31 is een oneven priemgetal, en 31 * x is hetzelfde als (x << 5) - x, zodat de JVM dit kan optimaliseren.

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

Hashwaarden voor primitieve typen

Voor elk primitief type is er een aanbevolen manier om een hash te berekenen:

  • int: gebruik de waarde zelf.
  • long: (int)(value ^ (value >>> 32)).
  • boolean: 1 of 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));
    }
}

Hashwaarden voor arrays

Roep hashCode() niet rechtstreeks aan op een array; de methode gebruikt identiteit, niet de inhoud.

Gebruik Arrays.hashCode(arr) voor een eenvoudige array of Arrays.deepHashCode(arr) voor geneste 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)));
    }
}

Houd equals en hashCode synchroon

De velden die in hashCode() worden gebruikt, moeten een deelverzameling zijn van de velden die in equals() worden gebruikt, bij voorkeur precies dezelfde.

Als equals meer velden vergelijkt dan hashCode, hebben twee gelijke objecten nog steeds dezelfde hash. Dat is toegestaan. Maar als hashCode een veld gebruikt dat equals negeert, schend je het contract.

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

De hashwaarde opslaan

Bij onveranderlijke objecten waarvoor het berekenen van de hash duur is, kun je het resultaat in een veld opslaan.

String doet dit intern precies zo. Doe dit alleen als het object echt onveranderlijk is, zodat de opgeslagen waarde nooit verouderd raakt.

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

De verdeling is belangrijk

Een goed verdeelde hashCode verspreidt sleutels gelijkmatig over de buckets. Laten we het aantal verschillende hashcodes voor een reeks objecten tellen.

Hoe meer verschillende waarden, hoe minder botsingen en hoe sneller de 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());
    }
}

Een voorbeeld van een slechte verdeling

Velden optellen zonder te vermenigvuldigen veroorzaakt botsingen: (1,2) en (2,1) hebben allebei hashwaarde 3.

De vermenigvuldigingsfactor 31 doorbreekt deze symmetrie, omdat de volgorde dan belangrijk is.

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

Geef voor waardetypen de voorkeur aan records

Voor pure gegevensdragers genereert een record automatisch een correcte, goed verdeelde hashCode.

Schrijf hashCode alleen met de hand als je aangepaste semantiek nodig hebt of geen record kunt gebruiken.

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 samenbrengen

Een volledige waardeklasse: onveranderlijke velden, equals en hashCode op basis van dezelfde velden, en een nette 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());
    }
}

Korte controle

Test je vaardigheden met hashCode.

Samenvatting

Je hebt geleerd hoe je hashCode correct implementeert:

  • Gebruik Objects.hash(...) voor het algemene geval.
  • Gebruik het patroon met de vermenigvuldigingsfactor 31 voor handgeschreven hashes.
  • Bereken hashes voor arrays met Arrays.hashCode, niet met de standaardmethode.
  • Houd de velden van hashCode synchroon met equals en geef de voorkeur aan records.

Vervolgens zie je hoe Java 8+ overvolle buckets in bomen omzet.

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        System.out.println("hashCode recap done: " + Objects.hash("done"));
    }
}
Gratis beginnen

Leer Java met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
104
Lessen
374

Veelgestelde vragen

Is de les “hashCode implementeren” gratis?

Ja — de volledige tekst van “hashCode implementeren” kun je hier gratis op het web lezen. Als je interactief wilt oefenen met een ingebouwde code-editor en een AI-begeleider die 24/7 beschikbaar is, en de rest van de cursus Java Academy wilt ontgrendelen, kun je upgraden naar CoddyKit PRO. De cursus Java Academy bevat in totaal 4 lessen.

Wat leer ik in “hashCode implementeren”?

Correcte hashfuncties schrijven Je oefent met Java Academy door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Java Academy te beginnen?

Ervaring vooraf is niet nodig. Java Academy op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 3 van 4.

Hoe lang duurt de les “hashCode implementeren”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Java Academy?

Ja. Elke les over Java Academy bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Hoe HashMap werkt
  2. Het equals/hashCode-contract
  3. hashCode implementeren
  4. Treeification en prestaties
← Terug naar Java Academy