In Java, i metodi equals() e hashCode() sono estremamente importanti per il corretto funzionamento delle collezioni come HashMap, HashSet e altre. Questa questione viene spesso trascurata dai programmatori alle prime armi, sebbene la violazione dei contratti di questi metodi porti a errori difficili da individuare nella logica delle applicazioni.
Storia della questione:
Nel linguaggio Java, inizialmente tutte le classi ereditano i metodi equals() e hashCode() dalla classe Object. Per impostazione predefinita, equals() confronta i riferimenti agli oggetti (cioè la loro posizione fisica in memoria), mentre hashCode() restituisce un codice unico per ogni oggetto. Tuttavia, per le classi personalizzate, è spesso necessario confrontare gli oggetti per contenuto piuttosto che per riferimento.
Problema:
Se i metodi equals() e hashCode() non vengono sovrascritti o vengono sovrascritti in modo errato, gli oggetti possono comportarsi in modo inaspettato nelle collezioni basate su hash. Questo porterà all'assenza di elementi, duplicazione o errori di ricerca.
Soluzione:
È importante sovrascrivere entrambi i metodi sempre insieme, rispettando rigorosamente il contratto:
a.equals(b) == true, allora a.hashCode() == b.hashCode()a.equals(b) == false, l'unicità non è richiesta per hashCodeEsempio di implementazione corretta:
public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }
Caratteristiche chiave:
equals() deve essere riflessivo, simmetrico, transitivo e consistente.hashCode() deve restituire lo stesso valore per un oggetto quando i dati non cambiano.È possibile utilizzare solo equals() senza hashCode() nelle classi che saranno memorizzate in HashSet?
No. Se hai sovrascritto solo equals(), le collezioni basate su hash non saranno in grado di determinare correttamente l'unicità degli oggetti. HashSet confronta prima hashCode, poi equals.
È obbligatorio utilizzare tutti i campi della classe in equals() e hashCode()?
No. Solo quelli significativi per l'identità logica della classe. Ad esempio, se un oggetto ha un identificatore interno unico, è sufficiente usarlo.
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); }
È possibile basarsi su un getter invece di un campo diretto in equals()?
In genere sì, se non ci sono effetti collaterali e il getter è stabile. Ma c'è il rischio che il getter restituisca valori diversi a chiamate diverse — in tal caso, il comportamento sarà imprevedibile.
hashCode() quando equals() è sovrascritto.hashCode().Un sviluppatore implementa la classe User e definisce solo il metodo equals(), dimenticando hashCode(). Di conseguenza, durante l'aggiunta e la ricerca di oggetti in HashSet si verificano duplicati e elementi "scomparsi".
Vantaggi:
Svantaggi:
Un sviluppatore implementa entrambi i metodi rigorosamente secondo il contratto, utilizzando solo l'id all'interno della logica di uguaglianza e hashing. Le collezioni si comportano come previsto e la ricerca e la memorizzazione funzionano correttamente.
Vantaggi:
Svantaggi: