Quali problemi dovrebbero essere considerati quando si sovrascrive equals e hashCode in Java?


Quali problemi / insidie devono essere considerati quando si sovrascrive equals e hashCode?

Author: George Stocker, 2008-08-26

11 answers

La teoria (per gli avvocati di lingua e matematicamente inclinato):

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 il oggetto 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 ignora uno, allora si dovrebbe ignorare l'altro.

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

Utilizzare le classi helper eccellenti EqualsBuilder e Il nostro sito utilizza cookie tecnici e di terze parti per migliorare la tua esperienza di navigazione. 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 si usa un hash-based dell'insieme 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. Il modo a prova di proiettile per garantire questo per rendere le tue chiavi immutabili, che ha anche altri vantaggi.

 1380
Author: Antti Sykäri, 2015-07-29 19:23:43

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

Gli oggetti caricati pigri sono sottoclassi

Se i tuoi oggetti vengono mantenuti utilizzando 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 proprie classe. Ciò significa che this.getClass() == o.getClass() restituirà false. Ad 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 o instanceof Person è l'unica cosa che si comporterà correttamente.

Gli oggetti caricati pigri hanno campi nulli

Gli ORM di solito usano i getter per forzare il caricamento di oggetti caricati pigri. Ciò significa che person.name sarà null se person è pigro caricato, anche se person.getName() forza il caricamento e restituisce "John Doe". Nella mia esperienza, questo emerge più spesso in hashCode() e equals().

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

Il salvataggio di un oggetto cambierà il suo stato

Gli oggetti persistenti usano spesso un campo id per tenere la chiave dell'oggetto. Questo campo verrà aggiornato automaticamente 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: tu impossibile includere getId() in hashCode(). Se lo fai, quando un oggetto viene mantenuto, il suo hashCode cambia. Se l'oggetto è in un HashSet, non lo troverai mai più.

Nel mio esempio Person, probabilmente userei 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 va bene per equals().

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

 281
Author: Johannes Brodwall, 2017-01-26 11:00:31

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

Questa affermazione è il risultato di equals() essere ereditarietà ostile. Il JLS (Java language specification) specifica che se A.equals(B) == true allora B.equals(A) deve anche restituire true. Se ometti quell'istruzione ereditando classi che sovrascrivono equals() (e ne modificano il comportamento) interromperà questa specifica.

Considera il seguente esempio di cosa succede 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 buono, 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 if b = a e il principio di sostituzione di Liskov chiamano super.equals(other) non solo nel caso di B istanza, ma controlla dopo per A istanza:

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 be un riferimento di classe A, perch lo estendi, in questo caso chiami super.equals() troppo.

 81
Author: Ran Biron, 2014-08-12 19:54:24

Per un'implementazione compatibile con l'ereditarietà, controlla la soluzione di Tal Cohen, Come posso implementare correttamente il metodo equals ()?

Sommario:

Nel suo libro Guida al linguaggio di programmazione Java efficace (Addison-Wesley, 2001), Joshua Bloch afferma che "Semplicemente non c'è modo di estendere una classe istanziabile e aggiungere un aspetto preservando il contratto equals."Tal non è d'accordo.

La sua soluzione è implementare equals () chiamando un altro non simmetrico Ciecamente Equals () in entrambi i modi. Ciecamente Equals () viene sovrascritto da sottoclassi, equals () viene 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 (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 di ereditarietà se il principio di sostituzione Liskov deve essere soddisfatto.

 42
Author: Kevin Wong, 2012-08-30 09:12:57

Ancora stupito che nessuno ha 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());
    }
 31
Author: Eugene, 2013-12-17 19:42:59

Ci sono due metodi nella classe super come java.lang.Oggetto. Abbiamo bisogno di sovrascriverli a oggetto 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 si desidera ottenere di più, si prega di controllare 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

Buon divertimento! @.@

 25
Author: Luna Kong, 2013-12-20 06:14:09

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

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

Io uso #1 in un'implementazione final uguale o quando si implementa un'interfaccia che prescrive un algoritmo per uguale (come le interfacce di raccolta java.util-il modo giusto per verificare con (obj instanceof Set) o qualsiasi altra interfaccia che si sta implementando). È generalmente una cattiva scelta quando gli uguali possono essere sovrascritti perché ciò interrompe la proprietà di simmetria.

Opzione #2 consente alla classe di essere estesa in modo sicuro senza sovrascrivere equals o rompere la simmetria.

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

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

}
 18
Author: erickson, 2008-08-29 16:15:27

Per uguale, esaminare Segreti di uguali di Angelika Langer. Mi piace molto. È anche una grande FAQ su Generici in Java. Guarda i suoi altri articoli qui (scorri verso il basso fino a "Core Java"), dove continua anche con Part-2 e "mixed type comparison". Buon divertimento a leggerli!

 15
Author: Johannes Schaub - litb, 2009-02-27 22:05:04

Equals() metodo viene utilizzato per determinare l'uguaglianza di due oggetti.

Poiché il 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 queste proprietà sono considerate. Non è necessario che tutte le proprietà debbano essere prese in considerazione per determinare l'uguaglianza e rispetto alla definizione e al contesto della classe può essere deciso. Quindi il metodo equals () può essere ignorare.

Dovremmo sempre ignorare il metodo hashCode() ogni volta che sovrascriviamo il metodo equals (). Se no, cosa succederà? Se usiamo hashtables nella nostra applicazione, non si comporterà 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 data è il metodo hashCode () nella classe Object utilizza l'indirizzo interno dell'oggetto e lo converte in integer e restituisce esso.

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
 11
Author: rohan kamat, 2013-11-13 21:27:49

Logicamente abbiamo:

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

Ma non viceversa!

 7
Author: Khaled.K, 2013-08-09 15:41:14

Un gotcha che ho trovato è dove due oggetti contengono riferimenti l'uno all'altro (un esempio è una relazione genitore/figlio con un metodo di convenienza sul genitore per ottenere tutti i figli).
Questi tipi di cose sono abbastanza comuni quando si eseguono mappature di ibernazione, ad esempio.

Se includi entrambe le estremità della relazione nel tuo hashCode o equals test, è possibile entrare in un ciclo ricorsivo che termina in una StackOverflowException.
La soluzione più semplice è non includere la collezione getChildren nei metodi.

 6
Author: Darren Greaves, 2008-09-02 21:06:47