Java Academy · Les

Het equals/hashCode-contract

Waarom beide moeten overeenkomen

Les 2 van 413 stappen

Het equals/hashCode-contract is een gratis Java Academy-les op CoddyKit. Dit is les 2 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.

Twee methoden, één contract

Elk Java-object erft equals() en hashCode() over van Object.

Wanneer je één methode overschrijft, moet je bijna altijd ook de andere overschrijven. Samen vormen ze een bindend contract waarop collecties op basis van hashes vertrouwen.

public class Main {
    public static void main(String[] args) {
        Object a = new Object();
        Object b = new Object();
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() == b.hashCode());
    }
}

De hoofdregel

Het contract bepaalt het volgende:

  • Als a.equals(b) waar is, moet a.hashCode() gelijk zijn aan b.hashCode().
  • Als hashcodes verschillen, zijn de objecten gegarandeerd niet gelijk.

Gelijke objecten moeten gelijke hashcodes hebben. Het omgekeerde is niet vereist.

public class Main {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hel" + "lo";
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode() == s2.hashCode());
    }
}

Ongelijke objecten mogen dezelfde hash hebben

Twee ongelijke objecten mogen dezelfde hashcode hebben. Dat is gewoon een botsing, en de map handelt die af met equals().

De enige verboden situatie is dus: gelijke objecten met verschillende hashcodes.

public class Main {
    public static void main(String[] args) {
        // Equal value, unequal objects, same hash is fine
        System.out.println("FB".hashCode() == "Ea".hashCode());
        System.out.println("FB".equals("Ea"));
    }
}

Een klasse die alleen equals overschrijft

Dit is een defecte klasse. De klasse overschrijft equals(), maar niet hashCode().

Twee logisch gelijke punten hebben nu verschillende hashcodes die ze van Object erven, waardoor het contract wordt geschonden.

public class Main {
    static class Point {
        final int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            if (!(o instanceof Point p)) return false;
            return x == p.x && y == p.y;
        }
        // BUG: no hashCode override
    }
    public static void main(String[] args) {
        Point a = new Point(1, 2);
        Point b = new Point(1, 2);
        System.out.println("equals: " + a.equals(b));
        System.out.println("same hash: " + (a.hashCode() == b.hashCode()));
    }
}

De fout in een HashSet

De defecte klasse werkt niet in een HashSet. De verzameling zoekt in de bucket die door de verkeerde hash is gekozen en vindt het gelijke element nooit.

Je houdt dan duplicaten over die afgewezen hadden moeten worden.

import java.util.HashSet;
import java.util.Set;

public class Main {
    static class Point {
        final int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            if (!(o instanceof Point p)) return false;
            return x == p.x && y == p.y;
        }
    }
    public static void main(String[] args) {
        Set<Point> set = new HashSet<>();
        set.add(new Point(1, 2));
        set.add(new Point(1, 2));
        System.out.println("size = " + set.size());
    }
}

De oplossing

Overschrijf beide methoden en leid ze af van dezelfde velden. Gelijke objecten hebben dan dezelfde hash en komen in dezelfde bucket terecht.

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class Main {
    static class Point {
        final int x, y;
        Point(int x, int y) { this.x = x; this.y = y; }
        @Override public boolean equals(Object o) {
            if (!(o instanceof Point p)) return false;
            return x == p.x && y == p.y;
        }
        @Override public int hashCode() { return Objects.hash(x, y); }
    }
    public static void main(String[] args) {
        Set<Point> set = new HashSet<>();
        set.add(new Point(1, 2));
        set.add(new Point(1, 2));
        System.out.println("size = " + set.size());
    }
}

equals moet reflexief zijn

Een correcte equals() voldoet aan vier eigenschappen. De eerste is reflexief: x.equals(x) moet waar zijn.

Dat klinkt vanzelfsprekend, maar aangepaste logica kan dit doorbreken als je vergeet rekening te houden met het geval waarin het om dezelfde referentie gaat.

public class Main {
    public static void main(String[] args) {
        String x = "java";
        System.out.println(x.equals(x));
    }
}

Symmetrisch en transitief

Nog twee regels:

  • Symmetrisch: als a.equals(b), dan ook b.equals(a).
  • Transitief: als a.equals(b) en b.equals(c), dan a.equals(c).

Het mengen van typen binnen een klassenhiërarchie doorbreekt vaak de symmetrie, dus vergelijk klassen zorgvuldig.

public class Main {
    public static void main(String[] args) {
        String a = "x", b = "x", c = "x";
        boolean symmetric = a.equals(b) == b.equals(a);
        boolean transitive = a.equals(b) && b.equals(c) && a.equals(c);
        System.out.println("symmetric: " + symmetric);
        System.out.println("transitive: " + transitive);
    }
}

Consistent

De vierde regel is consistent: herhaalde aanroepen geven hetzelfde resultaat zolang de objecten niet veranderen.

Daarom moet je equals en hashCode baseren op onveranderlijke velden. Als je na het invoegen een veld wijzigt dat in hashCode wordt gebruikt, raakt de map beschadigd.

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        // Immutable record gives consistent equals/hashCode automatically
        record Id(int value) {}
        Id id = new Id(7);
        System.out.println(Objects.equals(id, new Id(7)));
        System.out.println(id.hashCode() == new Id(7).hashCode());
    }
}

Records doen het voor je

Een Java record genereert automatisch equals() en hashCode() op basis van al zijn componenten.

Geef voor gegevens die zich als waarden gedragen de voorkeur aan records. Het contract wordt automatisch nageleefd en blijft synchroon.

import java.util.HashSet;
import java.util.Set;

public class Main {
    record Point(int x, int y) {}
    public static void main(String[] args) {
        Set<Point> set = new HashSet<>();
        set.add(new Point(1, 2));
        set.add(new Point(1, 2));
        System.out.println("size = " + set.size());
    }
}

De valkuil van veranderlijke sleutels

Als je een veld wijzigt dat in hashCode wordt gebruikt nadat je het object in een map hebt geplaatst, kun je het niet meer terugvinden.

De map zoekt in de oude bucket, maar het object heeft nu een nieuwe hash. De vermelding wordt een verloren spook.

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

public class Main {
    static class Box {
        int id;
        Box(int id) { this.id = id; }
        @Override public boolean equals(Object o) {
            return o instanceof Box b && b.id == id;
        }
        @Override public int hashCode() { return Objects.hash(id); }
    }
    public static void main(String[] args) {
        Map<Box, String> m = new HashMap<>();
        Box key = new Box(1);
        m.put(key, "value");
        key.id = 99; // mutated after insertion
        System.out.println(m.get(key));
    }
}

Korte controle

Test je begrip van het contract.

Samenvatting

Je hebt het equals/hashCode-contract geleerd:

  • Gelijke objecten moeten gelijke hashcodes hebben.
  • equals moet reflexief, symmetrisch, transitief en consistent zijn.
  • Baseer beide methoden op dezelfde onveranderlijke velden.
  • Records genereren gratis correcte implementaties.

Vervolgens schrijf je zelf met de hand een correcte hashCode.

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
    }
}
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 “Het equals/hashCode-contract” gratis?

Ja — de volledige tekst van “Het equals/hashCode-contract” 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 “Het equals/hashCode-contract”?

Waarom beide moeten overeenkomen 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 2 van 4.

Hoe lang duurt de les “Het equals/hashCode-contract”?

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