Quando fai l'override di equals() per definire l'uguaglianza logica, devi anche fare l'override di hashCode() — perché le collezioni basate su hash (, ) si basano sul contratto che . Violare questo porta a bug sottili e difficili da trovare.
Quando fai l'override di equals() per definire l'uguaglianza logica, devi anche fare l'override di hashCode() — perché le collezioni basate su hash (, ) si basano sul contratto che . Violare questo porta a bug sottili e difficili da trovare.
HashMapHashSet// 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
Senza fare l'override, due oggetti con contenuto identico non sono "uguali" — solitamente non è quello che vuoi per gli oggetti valore.
@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()
}
Il contratto: se a.equals(b) è true, allora a.hashCode() == b.hashCode() DEVE essere 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)!
Perché? HashMap/HashSet usano prima hashCode() per trovare il bucket giusto, poi equals() dentro di esso. Con il hashCode() predefinito (basato sull'identità), p1 e p2 finiscono in bucket diversi, quindi contains non li confronta mai — l'insieme pensa che siano diversi. Questo produce bug sconcertanti: duplicati in un Set, ricerche fallite nella mappa, ecc.
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!
I record Java (e la generazione tramite IDE / Lombok) producono equals/hashCode corretti e coerenti per te.
Il contratto equals/hashCode è una delle regole più importanti — e più comunemente violate — di Java.
Fare l'override di equals() senza hashCode() rompe le collezioni basate su hash in modi difficili da debuggare: gli oggetti che consideri uguali "scompaiono" in HashMap/HashSet (ricerche fallite, duplicati fantasma) perché fanno hash in bucket diversi.
Poiché queste collezioni sono onnipresenti, ogni classe di tipo valore usata come chiave o elemento di set deve fare l'override di entrambi in modo coerente.
Comprendere il perché (il meccanismo di ricerca bucket-poi-equals) — e usare i record o il codice generato per farlo correttamente — è essenziale per un comportamento corretto ed è un argomento classico nei colloqui che rivela una vera comprensione di come funzionano le collezioni Java.
Una raccolta di domande di colloquio IT con risposte dettagliate — da Junior a Senior.
Dona