equals/hashCode-sopimus
Miksi molempien on oltava yhteensopivia.
equals/hashCode-sopimus on ilmainen Java Academy-oppitunti CoddyKitissä. Tämä on oppitunti 2/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Java Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Java Academy-kurssilla on yhteensä 4 oppituntia.
Kaksi metodia, yksi sopimus
Jokainen Java-olio perii equals()- ja hashCode()-metodit luokasta Object.
Kun toinen niistä ylikirjoitetaan, myös toinen on lähes aina ylikirjoitettava. Ne muodostavat sitovan sopimuksen, johon hajautukseen perustuvat kokoelmat luottavat.
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());
}
}Perussääntö
Sopimus määrittelee seuraavaa:
- Jos
a.equals(b)on true, myösa.hashCode()- jab.hashCode()-arvojen on oltava samat. - Jos hajautusarvot eroavat toisistaan, oliot eivät varmasti ole samoja.
Samoilla olioilla on oltava samat hajautusarvot. Käänteistä ei vaadita.
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());
}
}Erisuurilla olioilla voi olla sama hajautusarvo
Kahdella erisuurella oliolla saa olla sama hajautusarvo. Kyseessä on vain törmäys, jonka map käsittelee equals()-metodilla.
Ainoa kielletty tilanne on siis se, että samoilla olioilla on eri hajautusarvot.
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"));
}
}Luokka, joka ylikirjoittaa vain equals-metodin
Tässä on rikkinäinen luokka. Se ylikirjoittaa equals()-metodin mutta ei hashCode()-metodia.
Kahdella loogisesti samalla pisteellä on nyt Object-luokasta perityt eri hajautusarvot, mikä rikkoo sopimusta.
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()));
}
}Virhe HashSetissä
Rikkinäinen luokka ei toimi HashSet-kokoelmassa. Joukko etsii väärän hajautusarvon valitsemasta lokerosta eikä koskaan löydä samaa alkiota.
Tuloksena on kaksoiskappaleita, jotka olisi pitänyt hylätä.
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());
}
}Korjaaminen
Ylikirjoittakaa molemmat metodit ja muodostakaa niiden tulokset samoista kentistä. Tällöin samat oliot saavat saman hajautusarvon ja päätyvät samaan lokeroon.
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-metodin on oltava refleksiivinen
Oikein toteutettu equals() noudattaa neljää ominaisuutta. Ensimmäinen on refleksiivisyys: x.equals(x) on oltava true.
Tämä vaikuttaa itsestään selvältä, mutta oma logiikka voi rikkoa sen, jos samaan viitteeseen viittaavaa tapausta ei käsitellä.
public class Main {
public static void main(String[] args) {
String x = "java";
System.out.println(x.equals(x));
}
}Symmetrinen ja transitiivinen
Kaksi muuta sääntöä:
- Symmetrinen: jos
a.equals(b), myösb.equals(a). - Transitiivinen: jos
a.equals(b)jab.equals(c), silloin myösa.equals(c).
Tyyppejä sekoitettaessa luokkahierarkiassa symmetria rikkoutuu usein, joten luokkia on verrattava huolellisesti.
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);
}
}Johdonmukainen
Neljäs sääntö on johdonmukaisuus: toistuvien kutsujen on palautettava sama tulos, kunhan oliot eivät muutu.
Siksi equals- ja hashCode-metodien perustana kannattaa käyttää muuttumattomia kenttiä. Jos hashCode-metodissa käytettyä kenttää muutetaan lisäyksen jälkeen, map rikkoutuu.
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());
}
}Recordit hoitavat tämän puolestasi
Java-record luo automaattisesti equals()- ja hashCode()-metodit kaikkien komponenttiensa perusteella.
Arvon kaltaiselle datalle kannattaa suosia recordeja. Sopimus täyttyy automaattisesti ja pysyy synkronoituna.
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());
}
}Muuttuvan avaimen ansa
Jos hashCode-metodissa käytettyä kenttää muutetaan sen jälkeen, kun olio on lisätty mapiin, oliota ei enää löydetä.
Map etsii vanhasta lokerosta, mutta olion hajautusarvo osoittaa nyt uuteen lokeroon. Tietueesta tulee kadonnut haamu.
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));
}
}Pikainen tarkistus
Testatkaa, miten hyvin hallitsette sopimuksen.
Kertaus
Opitte equals/hashCode-sopimuksen:
- Samoilla olioilla on oltava samat hajautusarvot.
- equals-metodin on oltava refleksiivinen, symmetrinen, transitiivinen ja johdonmukainen.
- Molempien metodien perustana on käytettävä samoja muuttumattomia kenttiä.
- Recordit luovat oikeat toteutukset automaattisesti.
Seuraavaksi kirjoitatte itse oikean hashCode-metodin.
import java.util.Objects;
public class Main {
public static void main(String[] args) {
System.out.println("Contract recap: " + Objects.hash(1, 2, 3));
}
}Opi Java tekoälytuutorin avulla — ilmaiseksi
Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.
- Kurssit
- 104
- Oppitunnit
- 374
Usein kysytyt kysymykset
Onko oppitunti ”equals/hashCode-sopimus” ilmainen?
Kyllä – oppitunnin ”equals/hashCode-sopimus” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Java Academy-kurssin, päivitä CoddyKit PROhon. Java Academy-kurssilla on yhteensä 4 oppituntia.
Mitä opin oppitunnilla ”equals/hashCode-sopimus”?
Miksi molempien on oltava yhteensopivia. Harjoittelet Java Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.
Tarvitsenko kokemusta aloittaakseni Java Academy-opiskelun?
Aiempi kokemus ei ole tarpeen. CoddyKitin Java Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 2/4.
Kuinka kauan ”equals/hashCode-sopimus”-oppitunnin suorittaminen kestää?
Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.
Voinko kirjoittaa ja suorittaa koodia tällä Java Academy-oppitunnilla?
Kyllä. Jokainen Java Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.
Kaikki tämän kurssin oppitunnit
- HashMapin toiminta
- equals/hashCode-sopimus
- hashCoden toteuttaminen
- Puumaistaminen ja suorituskyky