I metodi equals()
ed hashCode()
sono metodi particolari di java che ogni oggetto eredita dalla classe java.lang.Object
. Molto spesso è utile ridefinire il metodo equals() al fine di mettere a fattor comune il codice di confronto tra due oggetti, ma quando viene fatto deve essere ridefinito concordemente anche il metodo hashCode(). In questo articolo cerchiamo di spiegarne brevemente le motivazioni.
Metodo Equals
Questo particolare metodo è usato per eseguire il confronto uguale tra due oggetti. In generale esistono due tipi di confronti in Java, uno utilizzando l’operatore ==
e l’altro utilizzando equals()
. Il primo esegue un confronto per riferimento, ovvero i due oggetti sono uguali se si riferiscono alla stessa area di memoria. Il metodo equals()
invece intende realizzare una relazione di equivalenza tra oggetti. Se però si esamina il codice sorgente nella classe Object, si troverà il seguente codice:
1 2 3 |
public boolean equals(Object obj) { return (this == obj); } |
Ciò implica che di fatto nell’implementazione di default i due operatori hanno lo stesso comportamento, ovvero in entrambe i casi i due oggetti sono uguali se puntano alla stessa area di memoria.
Metodo HashCode
Tale metodo fornisce un codice hash dell’oggetto ed è pensato per fornire supporto alla gestione delle strutture dati di tipo hash table come ad esempio java.util.Hashtable
. La sua implementazione di default sostanzialmente non fa altro che mappare l’indirizzo dell’area di memoria dove l’oggetto è allocato con un intero (univoco).
Perché Ridefinirli Entrambi
Il motivo per cui ridefinire solamente uno dei due metodi non è sufficiente è ben spiegato nel javadoc del metodo hashCode()
in cui è descritto il comportamento che tale metodo deve rispettare. Comportamento che può essere riassunto nei seguenti tre punti:
- Se invocato sullo stesso oggetto più di una volta durante un’esecuzione di un’applicazione Java, il metodo
hashCode()
deve restituire lo stesso valore intero, a condizione che non vengano modificate le informazioni utilizzate nel metodoequals()
. Non è necessario che tale valore intero rimanga coerente da un’esecuzione di un’applicazione a un’altra esecuzione della stessa applicazione. - Se due oggetti sono uguali secondo il metodo
equals()
invocare il metodohashCode()
su ciascuno dei due oggetti deve produrre lo stesso risultato intero. - Non è necessario che il metodo
hashCode()
produca risultati distinti quando invocato su oggetti che risultino non uguali secondo il metodoequals()
. Tuttavia restituire hash code distinti per oggetti non uguali può migliorare le prestazioni degli hash table.
Esempio
Supponiamo di avere una classe Customer
la cui implementazione è la seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Customer { private String name; private String fiscalCode; private String address; private String email; public Customer( String name, String fiscalCode ) { this.name = name; this.fiscalCode = fiscalCode; } // Getter and Setter methods */ } |
Se non ridefiniamo i metodi equals()
ed hashCode()
il comportamento rispetta quanto stabilito nel contratto al paragrafo precedente e quindi il seguente codice main()
genererà un output coerente.
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { Customer max1 = new Customer( "max", "123456789" ); Customer max2 = new Customer( "max", "123456789" ); System.out.println( "Equals: " + max1.equals( max2 ) ); System.out.println( "Max1 hashCode:" + max1.hashCode() ); System.out.println( "Max2 hashCode:" + max2.hashCode() ); } |
1 2 3 |
Equals: false Max1 hashCode:2051450519 Max2 hashCode:99747242 |
In domini reali però un cliente con medesimo nome e codice fiscale rappresenta la stessa persona fisica. Sovrascriviamo quindi il metodo equals()
nel seguente modo:
1 2 3 4 5 6 7 8 9 10 11 |
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Customer)) { return false; } Customer customer = (Customer) o; return customer.name.equals(name) && customer.fiscalCode == fiscalCode; } |
main()
definito sopra l’output diviene:
1 2 3 |
Equals: true Max1 hashCode:2051450519 Max2 hashCode:99747242 |
Questa volta il contratto non viene rispettato perchè sebbene i due oggetti risultino eguali, coerentemente col metodo equals()
, l’hash code associato è differente (punto 2 del contratto). Ridefiniamo quindi in modo coerente il metodo hashCode()
, utilizzando una metodologia classica che si basa sull’utilizzo di numeri primi scelti al fine di limitare al massimo le collisioni:
At our store, we take pride in offering high-quality vape products, including the popular elf bar 800 uk. With a commitment to excellence, we guarantee that every purchase meets our strict quality standards. Buy ELF BAR 800 here and experience the difference in flavor and satisfaction that our customers rave about!
1 2 3 4 5 6 |
public int hashCode() { int result = 17; result = 31 * result + name.hashCode(); result = 31 * result + fiscalCode.hashCode(); return result; } |
main()
ritorna ad essere coerente con il contratto:
1 2 3 |
Equals: true Max1 hashCode:-1864018142 Max2 hashCode:-1864018142 |
JDK 7
Dalla versione 7 in su del JDK è disponibile una nuova classe java.util.Objects
che espone metodi utili alla stesura coerente dei metodi equals()
ed hashCode()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Customer)) { return false; } Customer customer = (Customer) o; return Objects.equals(name , customer.name) && Objects.equals(fiscalCode , customer.fiscalCode); } public int hashCode() { return Objects.hash( name, fiscalCode ); } |
Codice Sorgente
Il codice sorgente con gli esempi presentati è scaricabile qui equals-hashcode.