Quali problemi devono essere considerati quando si esegue l’override su uguaglianza e hashCode in Java?

Quali problemi / insidie ​​devono essere considerati quando si esegue l’override su equals e hashCode ?

La teoria (per gli avvocati linguistici e per quelli matematicamente inclini):

equals() ( javadoc ) deve definire una relazione di equivalenza (deve essere riflessiva , simmetrica e transitiva ). Inoltre, deve essere coerente (se gli oggetti non vengono modificati, deve continuare a restituire lo stesso valore). Inoltre, o.equals(null) deve sempre restituire false.

hashCode() ( javadoc ) deve essere coerente (se l’object non viene modificato in termini di equals() , deve continuare a restituire lo stesso valore).

La relazione tra i due metodi è:

Ogni volta che a.equals(b) , allora a.hashCode() deve essere uguale a b.hashCode() .

In pratica:

Se si esegue l’override di uno, è necessario ignorare l’altro.

Usa lo stesso set di campi che usi per calcolare equals() per calcolare hashCode() .

Usa le eccellenti classi di supporto EqualsBuilder e HashCodeBuilder dalla libreria di Apache Commons Lang . Un esempio:

 public class Person { private String name; private int age; // ... @Override public int hashCode() { return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers // if deriving: appendSuper(super.hashCode()). append(name). append(age). toHashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Person)) return false; if (obj == this) return true; Person rhs = (Person) obj; return new EqualsBuilder(). // if deriving: appendSuper(super.equals(obj)). append(name, rhs.name). append(age, rhs.age). isEquals(); } } 

Ricorda anche:

Quando utilizzi una raccolta o una mappa basata su hash come HashSet , LinkedHashSet , HashMap , Hashtable o WeakHashMap , assicurati che l’hashCode () degli oggetti chiave che hai inserito nella collezione non cambi mai mentre l’object si trova nella collezione. Il modo a prova di proiettile per garantire questo è rendere le tue chiavi immutabili, che ha anche altri vantaggi .

Ci sono alcuni problemi che vale la pena di notare se hai a che fare con classi che sono persistenti usando un Object-Relationship Mapper (ORM) come Hibernate, se non pensavi che questo fosse già irragionevolmente complicato!

Gli oggetti caricati pigri sono sottoclassi

Se i tuoi oggetti sono persistenti usando un ORM, in molti casi ti occuperai di proxy dinamici per evitare di caricare oggetti troppo presto dall’archivio dati. Questi proxy sono implementati come sottoclassi della tua class. Ciò significa che this.getClass() == o.getClass() restituirà false . Per esempio:

 Person saved = new Person("John Doe"); Long key = dao.save(saved); dao.flush(); Person retrieved = dao.retrieve(key); saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy 

Se hai a che fare con un ORM, usare l’ o instanceof Person è l’unica cosa che si comporterà correttamente.

Gli oggetti caricati pigri hanno campi null

Gli ORM usano solitamente i getter per forzare il caricamento di oggetti caricati pigri. Ciò significa che person.name sarà null se la person è pigra caricata, anche se person.getName() forza il caricamento e restituisce “John Doe”. Nella mia esperienza, questo appare più spesso in hashCode() ed equals() .

Se hai a che fare con un ORM, assicurati di usare sempre getter e mai riferimenti di campo in hashCode() ed equals() .

Salvare un object cambierà il suo stato

Gli oggetti persistenti usano spesso un campo id per contenere la chiave dell’object. Questo campo verrà automaticamente aggiornato quando un object viene salvato per la prima volta. Non utilizzare un campo id in hashCode() . Ma puoi usarlo in equals() .

Uno schema che uso spesso è

 if (this.getId() == null) { return this == other; } else { return this.getId().equals(other.getId()); } 

Ma: non è ansible includere getId() in hashCode() . Se lo fai, quando un object è persistente, il suo hashCode cambia. Se l’object si trova in un HashSet , non lo troverai mai più.

Nel mio esempio Person , probabilmente getId() getName() per hashCode e getId() più getName() (solo per paranoia) per equals() . Va bene se ci sono alcuni rischi di “collisioni” per hashCode() , ma mai okay per equals() .

hashCode() dovrebbe usare il sottoinsieme di proprietà non modificabile da equals()

Un chiarimento su obj.getClass() != getClass() .

Questa affermazione è il risultato equals() dell’ereditarietà dell’eredità. Il JLS (specifica del linguaggio Java) specifica che se A.equals(B) == true allora anche B.equals(A) deve restituire true . Se si omette che l’istruzione che eredita le classi che eseguono l’override di equals() (e ne modifichi il comportamento) interromperà questa specifica.

Considera il seguente esempio di cosa succede quando l’affermazione è omessa:

  class A { int field1; A(int field1) { this.field1 = field1; } public boolean equals(Object other) { return (other != null && other instanceof A && ((A) other).field1 == field1); } } class B extends A { int field2; B(int field1, int field2) { super(field1); this.field2 = field2; } public boolean equals(Object other) { return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other)); } } 

Facendo un new A(1).equals(new A(1)) Inoltre, il new B(1,1).equals(new B(1,1)) rivela vero, come dovrebbe.

Questo sembra tutto molto buono, ma guarda cosa succede se proviamo ad usare entrambe le classi:

 A a = new A(1); B b = new B(1,1); a.equals(b) == true; b.equals(a) == false; 

Ovviamente, questo è sbagliato.

Se si desidera garantire la condizione simmetrica. a = b se b = a e il principio di sostituzione di Liskov chiama super.equals(other) non solo nel caso di istanza B , ma controlla dopo per un’istanza A :

 if (other instanceof B ) return (other != null && ((B)other).field2 == field2 && super.equals(other)); if (other instanceof A) return super.equals(other); else return false; 

Che produrrà:

 a.equals(b) == true; b.equals(a) == true; 

Dove, se a non è un riferimento di B , allora potrebbe essere un riferimento di class A (perché lo estendi), in questo caso chiami anche super.equals() .

Per un’implementazione favorevole all’eredità, controlla la soluzione di Tal Cohen, Come implementare correttamente il metodo equals ()?

Sommario:

Nel suo libro Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch afferma che “Non c’è semplicemente alcun modo di estendere una class instantiable e aggiungere un aspetto preservando il contratto di uguaglianza”. Tal non è d’accordo.

La sua soluzione è implementare equals () chiamando un altro nonsymmetric blindlyEquals () in entrambe le direzioni. blindlyEquals () è sovrascritto dalle sottoclassi, equals () è ereditato e mai sovrascritto.

Esempio:

 class Point { private int x; private int y; protected boolean blindlyEquals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point)o; return (px == this.x && py == this.y); } public boolean equals(Object o) { return (this.blindlyEquals(o) && o.blindlyEquals(this)); } } class ColorPoint extends Point { private Color c; protected boolean blindlyEquals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint)o; return (super.blindlyEquals(cp) && cp.color == this.color); } } 

Si noti che equals () deve funzionare tra le gerarchie di ereditarietà se il Principio di sostituzione di Liskov deve essere soddisfatto.

Ancora stupito che nessuno abbia raccomandato la libreria guava per questo.

  //Sample taken from a current working project of mine just to illustrate the idea @Override public int hashCode(){ return Objects.hashCode(this.getDate(), this.datePattern); } @Override public boolean equals(Object obj){ if ( ! obj instanceof DateAndPattern ) { return false; } return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate()) && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern()); } 

Ci sono due metodi in super-class come java.lang.Object. Dobbiamo sostituirli all’object personalizzato.

 public boolean equals(Object obj) public int hashCode() 

Gli oggetti uguali devono produrre lo stesso codice hash purché siano uguali, tuttavia gli oggetti non uguali non devono produrre codici hash distinti.

 public class Test { private int num; private String data; public boolean equals(Object obj) { if(this == obj) return true; if((obj == null) || (obj.getClass() != this.getClass())) return false; // object must be Test at this point Test test = (Test)obj; return num == test.num && (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { int hash = 7; hash = 31 * hash + num; hash = 31 * hash + (null == data ? 0 : data.hashCode()); return hash; } // other methods } 

Se vuoi ottenere di più, controlla questo link come http://www.javaranch.com/journal/2002/10/equalhash.html

Questo è un altro esempio, http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

Divertiti! @. @

Ci sono un paio di modi per controllare l’uguaglianza di class prima di controllare l’uguaglianza dei membri, e penso che entrambi siano utili nelle giuste circostanze.

  1. Utilizzare l’operatore instanceof .
  2. Usa this.getClass().equals(that.getClass()) .

Io uso # 1 in un’implementazione final equals o quando implemento un’interfaccia che prescrive un algoritmo per equals (come le interfacce di raccolta java.util -il modo giusto per verificare con (obj instanceof Set) o qualsiasi altra interfaccia tu stia implementando) . È generalmente una ctriggers scelta quando è ansible sovrascrivere gli uguali perché ciò interrompe la proprietà di simmetria.

L’opzione n. 2 consente di estendere in sicurezza la class senza prevalere su uguali o rompere la simmetria.

Se la tua class è Comparable , anche i metodi equals e compareTo dovrebbero essere coerenti. Ecco un modello per il metodo equals in una class Comparable :

 final class MyClass implements Comparable { … @Override public boolean equals(Object obj) { /* If compareTo and equals aren't final, we should check with getClass instead. */ if (!(obj instanceof MyClass)) return false; return compareTo((MyClass) obj) == 0; } } 

Per gli uguali, guarda in Secrets of Equals di Angelika Langer . Mi piace veramente tanto. Lei è anche una grande FAQ su Generics in Java . Guarda i suoi altri articoli qui (scorri verso il basso fino a “Core Java”), dove continua anche con la parte 2 e “confronto di tipi misti”. Divertiti a leggerli!

Il metodo equals () è usato per determinare l’uguaglianza di due oggetti.

il valore int di 10 è sempre uguale a 10. Ma questo metodo equals () riguarda l’uguaglianza di due oggetti. Quando diciamo object, avrà proprietà. Per decidere sull’uguaglianza, quelle proprietà sono considerate. Non è necessario che tutte le proprietà siano prese in considerazione per determinare l’uguaglianza e rispetto alla definizione della class e al contesto in cui può essere decisa. Quindi il metodo equals () può essere sovrascritto.

dovremmo sempre sovrascrivere il metodo hashCode () ogni volta che sovrascriviamo il metodo equals (). Se no, cosa succederà? Se usiamo le hashtables nella nostra applicazione, non si comportano come previsto. Poiché l’hashCode viene utilizzato per determinare l’uguaglianza dei valori memorizzati, non restituirà il giusto valore corrispondente per una chiave.

L’implementazione predefinita fornita è il metodo hashCode () nella class Object utilizza l’indirizzo interno dell’object e lo converte in numero intero e lo restituisce.

 public class Tiger { private String color; private String stripePattern; private int height; @Override public boolean equals(Object object) { boolean result = false; if (object == null || object.getClass() != getClass()) { result = false; } else { Tiger tiger = (Tiger) object; if (this.color == tiger.getColor() && this.stripePattern == tiger.getStripePattern()) { result = true; } } return result; } // just omitted null checks @Override public int hashCode() { int hash = 3; hash = 7 * hash + this.color.hashCode(); hash = 7 * hash + this.stripePattern.hashCode(); return hash; } public static void main(String args[]) { Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3); Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2); Tiger siberianTiger = new Tiger("White", "Sparse", 4); System.out.println("bengalTiger1 and bengalTiger2: " + bengalTiger1.equals(bengalTiger2)); System.out.println("bengalTiger1 and siberianTiger: " + bengalTiger1.equals(siberianTiger)); System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode()); System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode()); System.out.println("siberianTiger hashCode: " + siberianTiger.hashCode()); } public String getColor() { return color; } public String getStripePattern() { return stripePattern; } public Tiger(String color, String stripePattern, int height) { this.color = color; this.stripePattern = stripePattern; this.height = height; } } 

Esempio di output del codice:

 bengalTiger1 and bengalTiger2: true bengalTiger1 and siberianTiger: false bengalTiger1 hashCode: 1398212510 bengalTiger2 hashCode: 1398212510 siberianTiger hashCode: –1227465966 

Logicamente abbiamo:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

Ma non viceversa!

Un trucco che ho trovato è dove due oggetti contengono riferimenti tra loro (un esempio è una relazione genitore / figlio con un metodo di convenienza sul genitore per ottenere tutti i figli).
Questo tipo di cose sono abbastanza comuni quando si eseguono mappature Hibernate, ad esempio.

Se includi entrambe le estremità della relazione nei test hashCode o equals è ansible entrare in un ciclo ricorsivo che termina con StackOverflowException.
La soluzione più semplice è non includere la raccolta getChildren nei metodi.