Quali problemi / insidie devono essere considerati quando si esegue l'override equals
e hashCode
?
Quali problemi / insidie devono essere considerati quando si esegue l'override equals
e hashCode
?
Risposte:
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 anche essere coerente (se l'oggetto non viene modificato in termini di equals()
, deve continuare a restituire lo stesso valore).
La relazione tra i due metodi è:
Ogni volta
a.equals(b)
, quindia.hashCode()
deve essere uguale ab.hashCode()
.
Se si esegue l'override di uno, è necessario ignorare l'altro.
Usa lo stesso set di campi che usi per calcolare equals()
per calcolarehashCode()
.
Usa le eccellenti classi di supporto EqualsBuilder e HashCodeBuilder dalla libreria Lang di Apache Commons . 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();
}
}
Quando si utilizza una raccolta o una mappa basata su hash come HashSet , LinkedHashSet , HashMap , Hashtable o WeakHashMap , assicurarsi che hashCode () degli oggetti chiave inseriti nella raccolta non cambi mai mentre l'oggetto si trova nella raccolta. Il modo antiproiettile per garantire ciò è rendere immutabili le tue chiavi, che ha anche altri vantaggi .
instanceof
restituisce false se il suo primo operando è null (di nuovo Java effettivo).
Ci sono alcuni problemi da notare se hai a che fare con classi che persistono usando un Object-Relationship Mapper (ORM) come Hibernate, se non pensavi che fosse già irragionevolmente complicato!
Gli oggetti caricati in modo pigro sono sottoclassi
Se i tuoi oggetti sono persistenti usando un ORM, in molti casi avrai a che fare con proxy dinamici per evitare di caricare l'oggetto troppo presto dall'archivio dati. Questi proxy sono implementati come sottoclassi della tua classe. Ciò significa che this.getClass() == o.getClass()
tornerà 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, l'utilizzo o instanceof Person
è l'unica cosa che si comporterà correttamente.
Gli oggetti caricati in modo pigro hanno campi null
Gli ORM di solito usano i getter per forzare il caricamento di oggetti carichi pigri. Questo significa che person.name
sarà null
se person
è pigro, anche se il person.getName()
caricamento delle forze e restituisce "John Doe". Nella mia esperienza, questo cresce più spesso in hashCode()
eequals()
.
Se hai a che fare con un ORM, assicurati di usare sempre getter e non inserire mai i riferimenti in hashCode()
e equals()
.
Il salvataggio di un oggetto cambierà il suo stato
Gli oggetti persistenti usano spesso un id
campo per contenere la chiave dell'oggetto. Questo campo verrà automaticamente aggiornato quando un oggetto viene salvato per la prima volta. Non utilizzare un campo ID in hashCode()
. Ma puoi usarlo in equals()
.
Un modello che uso spesso è
if (this.getId() == null) {
return this == other;
}
else {
return this.getId().equals(other.getId());
}
Ma: non puoi includere getId()
in hashCode()
. Se lo fai, quando un oggetto è persistente, hashCode
cambia. Se l'oggetto è in a HashSet
, "non" lo troverai mai più.
Nel mio Person
esempio, probabilmente userei getName()
for hashCode
e getId()
plus getName()
(solo per paranoia) per equals()
. Va bene se ci sono dei rischi di "collisioni" per hashCode()
, ma mai va bene equals()
.
hashCode()
dovrebbe usare il sottoinsieme non modificabile di proprietà da equals()
Saving an object will change it's state
! hashCode
deve tornare int
, quindi come utilizzerai getName()
? Puoi fare un esempio per il tuohashCode
Un chiarimento sul obj.getClass() != getClass()
.
Questa affermazione è il risultato equals()
dell'essere ereditarietà ostile. Il JLS (specifica del linguaggio Java) specifica che se A.equals(B) == true
poi B.equals(A)
deve anche tornare true
. Se si omette quell'istruzione che eredita le classi che sovrascrivono equals()
(e ne modificano il comportamento), si infrange questa specifica.
Considera il seguente esempio di ciò che accade quando l'istruzione viene 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 new A(1).equals(new A(1))
anche, new B(1,1).equals(new B(1,1))
risultato dare vero, come dovrebbe.
Sembra tutto molto bello, ma guarda cosa succede se proviamo a 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 = ae il principio di sostituzione di Liskov chiama super.equals(other)
non solo in caso di B
esempio, ma controlla dopo per A
esempio:
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 essere un riferimento di classe A
(perché si estenderla), in questo caso si chiama super.equals()
troppo .
ThingWithOptionSetA
può essere uguale a un a Thing
condizione che tutte le opzioni extra abbiano valori predefiniti, e allo stesso modo per a ThingWithOptionSetB
, allora dovrebbe essere possibile che un ThingWithOptionSetA
confronto sia uguale a un ThingWithOptionSetB
solo se tutte le proprietà non base di entrambi gli oggetti corrispondono ai loro valori predefiniti, ma Non vedo come fai a provarlo.
B b2 = new B(1,99)
, quindi b.equals(a) == true
e a.equals(b2) == true
ma b.equals(b2) == false
.
Per un'implementazione intuitiva, controlla la soluzione di Tal Cohen, Come posso implementare correttamente il metodo equals ()?
Sommario:
Nel suo libro Effective Java Programming Language Guide (Addison-Wesley, 2001), Joshua Bloch afferma che "Semplicemente non c'è modo di estendere una classe istantanea e aggiungere un aspetto preservando il contratto uguale". Tal non è d'accordo.
La sua soluzione è implementare equals () chiamando un altro non simmetrico blindlyEquals () in entrambi i modi. blindlyEquals () viene sovrascritto da sottoclassi, equals () viene ereditato e mai ignorato.
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 (p.x == this.x && p.y == 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 attraverso le gerarchie ereditarie se il Principio di sostituzione di Liskov deve essere soddisfatto.
if (this.getClass() != o.getClass()) return false
, ma flessibile in quanto restituisce falso solo se le classi derivate si preoccupano di modificare uguali. È giusto?
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());
}
this
in this.getDate()
non significa nulla (diverso dal disordine)
if (!(otherObject instanceof DateAndPattern)) {
. Concordo con Hernan e Steve Kuo (anche se quello è una questione di preferenze personali), ma comunque +1.
Esistono due metodi in super classe come java.lang.Object. Dobbiamo sovrascriverli su oggetti personalizzati.
public boolean equals(Object obj)
public int hashCode()
Gli oggetti uguali devono produrre lo stesso codice hash purché siano uguali, tuttavia gli oggetti disuguali 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! @. @
Esistono un paio di modi per verificare l'uguaglianza di classe prima di verificare l'uguaglianza dei membri e penso che entrambi siano utili nelle giuste circostanze.
instanceof
operatore.this.getClass().equals(that.getClass())
.Uso il numero 1 in final
un'implementazione uguale o quando implemento un'interfaccia che prescrive un algoritmo uguale (come le java.util
interfacce di raccolta) il modo giusto per verificare con(obj instanceof Set)
o qualunque interfaccia stai implementando). È generalmente una cattiva scelta quando è possibile ignorare gli uguali perché ciò rompe la proprietà di simmetria.
L'opzione n. 2 consente di estendere in modo sicuro la classe senza sovrascrivere uguali o interrompere la simmetria.
Se anche la tua classe è Comparable
, anche i metodi equals
e compareTo
dovrebbero essere coerenti. Ecco un modello per il metodo uguale in una Comparable
classe:
final class MyClass implements Comparable<MyClass>
{
…
@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;
}
}
final
, e il compareTo()
metodo è stato ignorato per invertire l'ordinamento, le istanze della sottoclasse e della superclasse non devono essere considerate uguali. Quando questi oggetti venivano usati insieme in un albero, le chiavi che erano "uguali" in base a instanceof
un'implementazione potrebbero non essere disponibili.
Per uguali, guarda in Secrets of Equals di Angelika Langer . Mi piace veramente tanto. È anche un'ottima 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 () viene utilizzato per determinare l'uguaglianza di due oggetti.
come valore int di 10 è sempre uguale a 10. Ma questo metodo equals () riguarda l'uguaglianza di due oggetti. Quando diciamo oggetto, avrà proprietà. Per decidere sull'uguaglianza si considerano tali proprietà. Non è necessario tenere conto di tutte le proprietà per determinare l'uguaglianza e rispetto alla definizione della classe e al contesto può essere deciso. Quindi il metodo equals () può essere ignorato.
dovremmo sempre sovrascrivere il metodo hashCode () ogni volta che sovrascriviamo il metodo equals (). In caso contrario, cosa accadrà? Se utilizziamo gli hashtable nella nostra applicazione, non funzionerà come previsto. Poiché hashCode viene utilizzato per determinare l'uguaglianza dei valori memorizzati, non restituirà il valore corrispondente corretto per una chiave.
L'implementazione predefinita fornita è il metodo hashCode () nella classe Object che utilizza l'indirizzo interno dell'oggetto, 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
Un gotcha che ho trovato è dove due oggetti contengono riferimenti reciproci (un esempio è una relazione genitore / figlio con un metodo di convenienza sul genitore per ottenere tutti i figli).
Questo genere di cose è abbastanza comune quando si eseguono mappature di Hibernate, ad esempio.
Se includi entrambe le estremità della relazione nel tuo hashCode o equivale ai test è possibile entrare in un ciclo ricorsivo che termina in StackOverflowException.
La soluzione più semplice è quella di non includere la raccolta getChildren nei metodi.
equals()
. Se uno scienziato pazzo creasse un mio duplicato saremmo equivalenti. Ma non avremmo lo stesso padre.