Kontrakten for equals/hashCode
Hvorfor begge skal stemme overens
Kontrakten for equals/hashCode er en gratis Java Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Java Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Java Academy-kurset indeholder 4 lektioner i alt.
To metoder, én kontrakt
Ethvert Java-objekt nedarver equals() og hashCode() fra Object.
Når du tilsidesætter den ene, skal du næsten altid også tilsidesætte den anden. De udgør en bindende kontrakt, som hash-baserede samlinger er afhængige af.
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());
}
}Den grundlæggende regel
Kontrakten siger:
- Hvis
a.equals(b)er true, skala.hashCode()være lig medb.hashCode(). - Hvis hashkoderne er forskellige, er objekterne garanteret forskellige.
Ens objekter skal have ens hashkoder. Det omvendte er ikke et krav.
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());
}
}Forskellige objekter kan have samme hashkode
To forskellige objekter må gerne have samme hashkode. Det er blot en kollision, og map'et håndterer den med equals().
Den eneste forbudte situation er derfor, at ens objekter har forskellige 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, der kun tilsidesætter equals
Her er en defekt klasse. Den tilsidesætter equals(), men ikke hashCode().
To logisk ens punkter har nu forskellige hashkoder, der er nedarvet fra Object, hvilket overtræder 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()));
}
}Fejlen i et HashSet
Den defekte klasse fungerer ikke i et HashSet. Sættet leder i den bucket, som den forkerte hashkode valgte, og finder aldrig det ens element.
Resultatet er dubletter, som skulle være blevet afvist.
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());
}
}Sådan rettes det
Tilsidesæt begge metoder, og beregn dem ud fra de samme felter. Så får ens objekter samme hashkode og havner i den samme bucket.
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 skal være refleksiv
En korrekt equals() overholder fire egenskaber. Den første er refleksivitet: x.equals(x) skal være true.
Det lyder indlysende, men brugerdefineret logik kan bryde det, hvis du glemmer at håndtere tilfældet med den samme reference.
public class Main {
public static void main(String[] args) {
String x = "java";
System.out.println(x.equals(x));
}
}Symmetrisk og transitiv
To regler mere:
- Symmetrisk: Hvis
a.equals(b), så erb.equals(a). - Transitiv: Hvis
a.equals(b)ogb.equals(c), så era.equals(c).
Hvis du blander typer på tværs af et klassehierarki, bryder det ofte symmetrien, så sammenlign klasser omhyggeligt.
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 regel er konsistens: Gentagne kald returnerer det samme resultat, så længe objekterne ikke ændres.
Derfor bør du basere equals og hashCode på uforanderlige felter. Hvis du ændrer et felt, der bruges i hashCode, efter indsættelse, ødelægger du tabellen.
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 gør det for dig
En Java record genererer automatisk equals() og hashCode() ud fra alle sine komponenter.
Til værdilignende data bør du foretrække records. Kontrakten er opfyldt uden ekstra arbejde og forbliver synkroniseret.
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());
}
}Fælden med foranderlige nøgler
Hvis du ændrer et felt, der bruges i hashCode, efter at du har lagt objektet i en tabel, kan du ikke længere finde det.
Tabellen leder i den gamle bucket, mens objektet nu hashes til en ny. Posten bliver et forsvundet spøgelse.
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));
}
}Hurtigt tjek
Test din forståelse af kontrakten.
Opsummering
Du har lært equals/hashCode-kontrakten:
- Ens objekter skal have ens hashkoder.
- equals skal være refleksiv, symmetrisk, transitiv og konsistent.
- Basér begge metoder på de samme uforanderlige felter.
- Records genererer korrekte implementeringer uden ekstra arbejde.
Nu skal du selv skrive en korrekt hashCode i hånden.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
}
}Lær Java med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 104
- Lektioner
- 374
Ofte stillede spørgsmål
Er lektionen “Kontrakten for equals/hashCode” gratis?
Ja — hele teksten til “Kontrakten for equals/hashCode” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Java Academy-kurset, skal du opgradere til CoddyKit PRO. Java Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Kontrakten for equals/hashCode”?
Hvorfor begge skal stemme overens Du øver dig i Java Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Java Academy?
Der kræves ingen tidligere erfaring. Java Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.
Hvor lang tid tager lektionen “Kontrakten for equals/hashCode”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Java Academy-lektion?
Ja. Alle Java Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Sådan fungerer HashMap
- Kontrakten for equals/hashCode
- Implementering af hashCode
- Treeification og ydeevne