Când anulezi equals() pentru a defini egalitatea logică, trebuie să anulezi și hashCode() — pentru că colecțiile bazate pe hash (HashMap, ) se bazează pe contractul că . Încălcarea acestui contract duce la bug-uri subtile și greu de găsit.
Când anulezi equals() pentru a defini egalitatea logică, trebuie să anulezi și hashCode() — pentru că colecțiile bazate pe hash (HashMap, ) se bazează pe contractul că . Încălcarea acestui contract duce la bug-uri subtile și greu de găsit.
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
Fără anulare, două obiecte cu conținut identic nu sunt "egale" — de obicei nu asta dorești pentru obiecte-valoare.
@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()
}
Contractul: dacă a.equals(b) este adevărat, atunci a.hashCode() == b.hashCode() TREBUIE să fie adevărat.
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)!
De ce? HashMap/HashSet folosesc mai întâi hashCode() pentru a găsi bucket-ul corect, apoi equals() în cadrul acestuia. Cu hashCode()-ul implicit (bazat pe identitate), p1 și p2 se găsesc în bucket-uri diferite, deci contains nu le compară niciodată — setul crede că sunt diferite. Aceasta produce bug-uri desconcertante: duplicate-uri într-un Set, căutări în map eșuate, etc.
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!
Recordurile Java records (și generarea de IDE / Lombok) produc equals/hashCode corect și consistent pentru tine.
Contractul equals/hashCode este una dintre cele mai importante — și cele mai frecvent încălcate — reguli ale Java.
Anularea equals() fără hashCode() rupe colecțiile bazate pe hash în moduri greu de depanat: obiectele pe care le consideri egale se "pierd" în HashMap/HashSet (căutări eșuate, duplicate-uri fantomă) pentru că au hash code-uri care duc la bucket-uri diferite.
Deoarece aceste colecții sunt omniprezente, fiecare clasă de tip valoare folosită ca cheie sau element de set trebuie să anuleze ambele în mod consistent.
Înțelegerea de ce (mecanismul de căutare bucket-apoi-equals) — și folosirea de recorduri sau cod generat pentru a o face corect — este esențială pentru comportament corect și este un subiect clasic de interviu care arată înțelegerea reală a modului în care funcționează colecțiile Java.