Quali sono i problemi che devono essere considerati quando si esegue l'override di equals e hashCode in Java?

StackOverflow https://stackoverflow.com/questions/27581

Domanda

Quali problemi / insidie che devono essere considerati quando si esegue l'override equals e hashCode?

È stato utile?

Soluzione

La teoria (per la lingua avvocati e matematicamente inclinato):

equals() (javadoc) deve definire una relazione di equivalenza (deve essere riflessivo, simmetrica, e transitivo).Inoltre, deve essere coerente (se gli oggetti non vengono modificate, deve mantenere la restituzione dello stesso valore).Inoltre, o.equals(null) deve sempre restituire false.

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

Il relazione tra i due metodi è:

Ogni volta che a.equals(b), poi a.hashCode() deve essere lo stesso b.hashCode().

In pratica:

Se si esegue l'override di uno, allora si dovrebbe ignorare l'altro.

Utilizzare lo stesso insieme di campi che si utilizza per calcolare equals() per calcolare hashCode().

Utilizzare l'eccellente classi di supporto EqualsBuilder e HashCodeBuilder dal Apache Commons Lang biblioteca.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();
    }
}

Ricordiamo inoltre:

Quando si utilizza un hash-based Collezione o Mappa come HashSet, LinkedHashSet, HashMap, Hashtable, o WeakHashMap, assicurarsi che l'hashCode() degli oggetti chiave che hai messo in collezione non cambia mai, mentre l'oggetto è in collezione.La prova di proiettile modo per garantire questo è quello di rendere le vostre chiavi, immutabile, che ha anche altri vantaggi.

Altri suggerimenti

Ci sono alcuni problemi degni di nota, se hai a che fare con classi che sono persistenti di utilizzo di un Oggetto-Relazione Mapper (ORM) come Hibernate, se si non credo che questo era eccessivamente complicato già!

Pigro caricato oggetti sono sottoclassi

Se gli oggetti sono persistenti nell'utilizzo di un ORM, in molti casi, si tratta di essere dinamico proxy per evitare il caricamento di un oggetto troppo presto per l'archivio dati.Questi proxy sono implementati come sottoclassi di una classe personalizzata.Questo significa chethis.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, utilizzando o instanceof Person è l'unica cosa che si comportano correttamente.

Pigro caricato oggetti null-campi

Orm di solito uso il getter per forzare il caricamento pigro caricato oggetti.Questo significa che person.name sarà null se person è pigro caricato, anche se person.getName() le forze di carico e restituisce "John Doe".Nella mia esperienza, questa colture più spesso in hashCode() e equals().

Se hai a che fare con un ORM, assicurarsi di utilizzare sempre getter, e mai i riferimenti di campo in hashCode() e equals().

Il salvataggio di un oggetto cambia il suo stato

Gli oggetti persistenti spesso di utilizzare un id campo per tenere premuto il tasto dell'oggetto.In questo campo viene aggiornato automaticamente quando un oggetto viene salvato prima.Non utilizzare un campo id in hashCode().Ma si può usare in equals().

Un modello che uso spesso è

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

Ma:non è possibile includere getId() in hashCode().Se si, quando un oggetto viene mantenuta, la sua hashCode i cambiamenti.Se l'oggetto è in HashSet, si ' ll mai trovare di nuovo.

Nel mio Person esempio, io probabilmente avrei uso getName() per hashCode e getId() plus getName() (solo per paranoia) per equals().Va bene se ci sono alcuni rischi di "collisioni" per hashCode(), ma non va bene per equals().

hashCode() dovrebbe utilizzare non cambia sottoinsieme di proprietà dal equals()

Un chiarimento circa la obj.getClass() != getClass().

Questa affermazione è il risultato di equals() essendo eredità ostile.I JLS (Java language specification) di specificare che, se A.equals(B) == true quindi B.equals(A) deve anche restituire true.Se si omette che la dichiarazione di ereditare le classi che hanno la precedenza equals() (e di modificare il suo comportamento) rompere questa specifica.

Si consideri il seguente esempio di cosa succede quando la dichiarazione è 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)) Inoltre, new B(1,1).equals(new B(1,1)) risultato danno vero, come dovrebbe.

Questo sembra tutto molto buono, ma guardate cosa succede se si tenta di utilizzare 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 simmetrica condizione.a=b se b=a e la sostituzione di Liskov principio di chiamata super.equals(other) non solo nel caso di B esempio, ma controllare 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 sarà in uscita:

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

Dove, se a non è un riferimento di B, quindi potrebbe essere un riferimento di classe A (perché si estende), in questo caso si chiama super.equals() troppo.

Eredità-friendly attuazione, check-out Tal Cohen soluzione, Come Faccio a Implementare Correttamente il Metodo equals ()?

Sommario:

Nel suo libro Efficace Del Linguaggio Di Programmazione Java Guida (Addison-Wesley, 2001), Joshua Bloch afferma che "semplicemente non C'è modo di estendere istanze di classe e di aggiungere un aspetto mantenendo uguale a contratto". Tal non è d'accordo.

La sua soluzione è implementare equals() chiamando un altro il blindlyEquals() in entrambi i modi.blindlyEquals() è escluso dalle sottoclassi, equals() ereditato, e mai sostituiti.

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);
    }
}

Nota che equals() deve lavorare attraverso le gerarchie di ereditarietà se il Liskov Principio Di Sostituzione è da essere soddisfatti.

Ancora stupito che nessuno raccomandato il guava libreria 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 di super classe java.lang.Oggetto.Abbiamo bisogno di ignorarle per oggetto personalizzato.

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

Oggetti uguali deve produrre lo stesso codice hash finchè sono uguali, tuttavia disparità di oggetti non è necessario produrre distinti codici hash.

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 si desidera ottenere di più, si prega di controllare questo link 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

Buon Divertimento!@.@

Ci sono un paio di modi per fare il check per l'uguaglianza delle classi prima di controllare gli stati di uguaglianza, e penso che entrambi sono utili nelle giuste circostanze.

  1. Utilizzare il instanceof operatore.
  2. Utilizzare this.getClass().equals(that.getClass()).

Io uso il #1 in un final uguale realizzazione o l'implementazione di un'interfaccia che prescrive un algoritmo per equals (come il java.util collezione di interfacce—il modo giusto per controllare con (obj instanceof Set) o qualsiasi interfaccia di implementazione).È generalmente una cattiva scelta quando è uguale può essere ignorata perché rompe la simmetria di proprietà.

Opzione #2 consente alla classe di essere tranquillamente esteso, senza l'override del metodo equals o di rottura di simmetria.

Se la tua classe è anche Comparable, il equals e compareTo i metodi devono essere coerenti troppo.Ecco un modello per il metodo equals in un 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;
  }

}

Per il pari, guardare in Segreti di Uguale da Angelika Langer.Mi piace molto.Lei è anche un grande FAQ a riguardo Generics in Java.Gli altri articoli qui (scorrere verso il basso per "Core Java"), dove si continua con la Parte-2, e "misti tipo di confronto".Divertitevi a leggere i loro!

metodo equals() è utilizzato per determinare l'uguaglianza di due oggetti.

come int il valore 10 è sempre uguale a 10.Ma questo metodo equals() è di circa uguaglianza di due oggetti.Quando diciamo che un oggetto non avrà proprietà.Per decidere sulla parità, tali proprietà sono considerati.Non è necessario che tutte le proprietà devono essere presi in considerazione per stabilire l'uguaglianza e il rispetto per la definizione della classe e nel contesto può essere deciso.Quindi il metodo equals() può essere sottoposto a override.

si dovrebbe sempre eseguire l'override di hashCode() metodo ogni volta che si esegue l'override di equals() metodo.Se non, cosa succederà?Se usiamo hashtable nella nostra applicazione, non si comporta come previsto.Come l'hashCode è utilizzato per determinare l'uguaglianza dei valori memorizzati, non verrà restituito il giusto valore corrispondente di un tasto.

L'implementazione di Default di dato è hashCode() il metodo in Oggetto classe utilizza l'indirizzo interno dell'oggetto e la 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 Di 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 vice-versa!

Un aspetto negativo che ho trovato è dove due oggetti contengono riferimenti alla vicenda (come ad esempio una relazione padre/figlio con una convenienza metodo principale per ottenere tutti i bambini).
Questi tipi di cose sono abbastanza comune quando si fa una mappatura Hibernate per esempio.

Se si include entrambe le estremità della relazione nella tua hashCode o uguale test è possibile entrare in un ciclo ricorsivo che termina in un StackOverflowException.
La soluzione più semplice è quella di non includere il getChildren raccolta di metodi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top