Kai perrašote equals() apibrėždami loginę lygybę, turite taip pat perrašyti hashCode() — nes į hash-ą pagrįstos kolekcijos (HashMap, ) pasikliauja kontraktu, kad . Šio reikalavimo pažeidimas veda prie subtilių ir sunkiai surandamų klaidų.
Kai perrašote equals() apibrėždami loginę lygybę, turite taip pat perrašyti hashCode() — nes į hash-ą pagrįstos kolekcijos (HashMap, ) pasikliauja kontraktu, kad . Šio reikalavimo pažeidimas veda prie subtilių ir sunkiai surandamų klaidų.
HashSet// by default, equals() compares IDENTITY (same object?), hashCode() is based on memory address
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30);
p1.equals(p2); // false by default — different objects, even with same data
Be perrašymo, du objektai su identiniu turiniu nėra "lygūs" — paprastai tai ne tai, ko norite vertės objektams.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person p = (Person) o;
return age == p.age && Objects.equals(name, p.name); // compare by CONTENT
}
@Override
public int hashCode() {
return Objects.hash(name, age); // MUST be consistent with equals()
}
Kontraktas: jei a.equals(b) grąžina true, tada a.hashCode() == b.hashCode() turi būti true.
Person p1 = new Person("Ann", 30);
Person p2 = new Person("Ann", 30); // equal by our equals()
Set<Person> set = new HashSet<>();
set.add(p1);
set.contains(p2); // ❌ likely FALSE — even though p1.equals(p2)!
Kodėl? HashMap/HashSet pirmiausia naudoja hashCode() norint rasti tinkamą kibimą, tada equals() jame. Su numatytuoju (tapatybės pagrindu grįstu) hashCode(), p1 ir p2 patenka į skirtingus kibimus, todėl contains jų niekada net nepalygina — rinkinys mano, kad jie skirtingi. Tai sukelia painus klaidų: dublikatus rinkinyje, nepavykusias žemėlapio paieškas ir panašiai.
1. equal objects → equal hash codes (REQUIRED for correctness)
2. unequal objects MAY have the same hash (collisions are allowed)
3. hashCode() must be consistent (same object → same code, unchanged)
4. equals() must be reflexive, symmetric, transitive, consistent
record Person(String name, int age) {} // records auto-generate equals/hashCode/toString!
Java records (ir IDE generavimas / Lombok) jums automatiškai generuoja teisingus ir nuoseklius equals/hashCode.
equals/hashCode kontraktas yra viena iš svarbiausių — ir dažniausiai pažeidžiamų — Java taisyklių.
equals() perrašymas be hashCode() sulaužo į hash-ą pagrįstas kolekcijas subtiliais būdais, kuriuos sunku išklaidi: objektai, kurie jums atrodo lygūs, "dingsta" HashMap/HashSet (nepavykusios paieškos, fantominiai dublikatai), nes jie iš hash formulės gaunami į skirtingus kibimus.
Kadangi šios kolekcijos yra visur, kiekviena vertės tipo klasė, naudojama kaip raktas arba rinkinio elementas, turi perrašyti abi nuosekliai.
Kodėl (kibimo-tada-equals paieškos mechanizmas) supratimas — ir naudojant records arba sugeneruotą kodą tam tinkamai — yra būtina teisingam elgesiui ir klasikinė pokalbio tema, kuri atskleidžia tikrą supratimą apie tai, kaip veikia Java kolekcijos.