Java Academy · leksjon

Kontrakten for equals/hashCode

Hvorfor begge må stemme overens

Leksjon 2 av 413 trinn

Kontrakten for equals/hashCode er en gratis leksjon i Java Academy på CoddyKit. Dette er leksjon 2 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Java Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Java Academy inneholder totalt 4 leksjoner.

To metoder, én kontrakt

Alle Java-objekter arver equals() og hashCode() fra Object.

Når De overstyrer den ene, må De nesten alltid også overstyre den andre. De utgjør en bindende kontrakt som hash-baserte samlinger er avhengige av.

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

Hovedregelen

Kontrakten sier:

  • Hvis a.equals(b) er true, må a.hashCode() være lik b.hashCode().
  • Hvis hashkodene er forskjellige, er objektene garantert ulike.

Like objekter må ha like hashkoder. Det omvendte er ikke nødvendig.

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

Ulike objekter kan ha samme hash

To ulike objekter kan ha samme hashkode. Det er bare en kollisjon, og mapet håndterer den med equals().

Den eneste forbudte situasjonen er altså at like objekter har forskjellige hashkoder.

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

En klasse som bare overstyrer equals

Her er en feilaktig klasse. Den overstyrer equals(), men ikke hashCode().

To logisk like punkter har nå forskjellige hashkoder som er arvet fra Object, noe som bryter kontrakten.

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

Feilen i en HashSet

Den feilaktige klassen mislykkes i en HashSet. Settet leter i bøtten som velges av den feilaktige hashen, og finner aldri det like elementet.

De ender opp med duplikater som burde ha blitt avvist.

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

Slik løser De det

Overstyr begge metodene og utled dem fra de samme feltene. Da deler like objekter samme hash og havner i samme bøtte.

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 må være refleksiv

En korrekt equals() følger fire egenskaper. Den første er refleksiv: x.equals(x) må være true.

Dette virker innlysende, men egendefinert logikk kan bryte det hvis De glemmer å håndtere tilfellet med samme referanse.

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

Symmetrisk og transitiv

To regler til:

  • Symmetrisk: hvis a.equals(b), så b.equals(a).
  • Transitiv: hvis a.equals(b) og b.equals(c), så a.equals(c).

Å blande typer på tvers av et klassehierarki bryter ofte symmetrien, så sammenlign klassene nøye.

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

Konsistent

Den fjerde regelen er konsistent: gjentatte kall returnerer samme resultat så lenge objektene ikke endres.

Det er derfor De bør basere equals og hashCode på uforanderlige felt. Hvis De endrer et felt som brukes i hashCode etter innsetting, blir mapet ødelagt.

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 gjør det for Dem

En Java record genererer automatisk equals() og hashCode() fra alle komponentene.

For data som representerer verdier, bør De foretrekke records. Kontrakten oppfylles automatisk og forblir synkronisert.

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

Fellen med muterbare nøkler

Hvis De endrer et felt som brukes i hashCode etter at De har lagt objektet inn i et map, kan De ikke lenger finne det.

Mapet leter i den gamle bøtten; objektet hashes nå til en ny. Oppføringen blir et tapt spøkelse.

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

Kort test

Test forståelsen Deres av kontrakten.

Oppsummering

De har lært equals/hashCode-kontrakten:

  • Like objekter må ha like hashkoder.
  • equals må være refleksiv, symmetrisk, transitiv og konsistent.
  • Basér begge metodene på de samme uforanderlige feltene.
  • Records genererer korrekte implementasjoner automatisk.

Deretter skal De skrive Deres egen korrekte hashCode for hånd.

import java.util.Objects;

public class Main {
    public static void main(String[] args) {
        System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
    }
}
Gratis å komme i gang

Lær deg Java med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
104
Leksjoner
374

Ofte stilte spørsmål

Er leksjonen «Kontrakten for equals/hashCode» gratis?

Ja – hele teksten i «Kontrakten for equals/hashCode» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av Java Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i Java Academy inneholder totalt 4 leksjoner.

Hva lærer jeg i «Kontrakten for equals/hashCode»?

Hvorfor begge må stemme overens Du øver på Java Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Java Academy?

Ingen tidligere erfaring er nødvendig. Java Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Kontrakten for equals/hashCode»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Java Academy-leksjonen?

Ja. Alle Java Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Slik fungerer HashMap
  2. Kontrakten for equals/hashCode
  3. Implementere hashCode
  4. Treeification og ytelse
← Tilbake til Java Academy