2015-10-13 14 views
19

Ho una classe Java, ecco un esempio:Controllare se due oggetti sono completamente uguali in Java

public class Car { 

    private int fuelType; 
    private Date made; 
    private String name; 
. 
. 
. // and so on 

Ora diciamo che ho due oggetti di auto e voglio mettere a confronto se tutte le loro variabili sono uguali .

In questo momento, ho risolto questo metodo con il metodo di priorità equals(Object o) e controllo se tutte le variabili corrispondono in entrambi gli oggetti.

Il problema qui è che se ho 20 classi, dovrò eseguire l'override di equals(Object o) in ognuna di esse.

C'è un modo per creare una sorta di metodo universale che possa confrontare uno dei due oggetti che ho passato e farmi sapere se corrispondono a ogni singola variabile o no?

+4

se si utilizza eclipse tasto destro del mouse -> origine-> genera uguali e hash. gli uguali generati si confronteranno usando qualunque sia il campo con cui lo si imposta. a proposito, sì, questo è il metodo std 'Object.equals (Object other)'. – eduyayo

+5

Sì. Puoi creare un helper che usa un po 'di riflessione e finire con un codice fragile che è * limitato * a creare problemi ad un certo punto. Oppure puoi risucchiarlo una volta e utilizzare le finestre di 20 generazione offerte dal tuo IDE. –

+1

Questo è il modo più semplice, è anche possibile utilizzare la riflessione: http://stackoverflow.com/questions/1449001/is-there-a-java-reflection-utility-to-do-a-deep-comparison-of-two- oggetti –

risposta

20

Avete alcune opzioni per l'automazione di Equals & hashCode (opzione # 3 saltato la mia mente !):

  1. IDE. Non lo consiglierei per la maggior parte degli oggetti in quanto possono lentamente andare alla deriva con la definizione della classe effettiva. Hanno anche un aspetto brutto e inquinano il tuo codebase con il codice boilerplate.
  2. Apache Commons ha un sacco di cose per semplificare questo processo, incluso uno reflective version, quindi nessun rischio di andare alla deriva obsoleto con la definizione della classe. È meglio del # 1, a meno che non si richieda un numero di hash o di un codice veloce, ma è ancora troppo standard per i miei gusti.
  3. Project Lombok e elaborazione annotazione. Colpisci un'annotazione EqualsAndHashCode sulla classe ya e fallo con esso. Raccomando di utilizzare Project Lombok. Aggiunge un tocco di magia nella build (ma non molto) e quindi richiede un plugin per il tuo IDE per comportarsi bene, ma sono un piccolo prezzo da pagare per nessun codice boilerplate. Lombok è un processore di annotazioni che viene eseguito in fase di compilazione in modo da non avere alcun impatto sulle prestazioni di runtime.
  4. Utilizzo di una lingua diversa che supporta la casella, ma anche la JVM. Groovy utilizza uno annotation e Kotlin supporta data classes. A meno che il tuo codice esistente possa essere rapidamente convertito, eviterei questo.
  5. Google's Auto ha uno AutoValue.Come Progetto Lombok questo è un processore di annotazione, tuttavia ha meno magia a scapito di poco più boilerplate (grazie a Louis Wasserman)
+1

In alternativa, se non ti piacciono i metodi generati magicamente, https://github.com/google/auto/ tree/master/value raggiunge obiettivi simili a Lombok con meno magia. [Questa sezione] (https://github.com/google/auto/tree/master/value#alternatives) discute alcune delle differenze con Lombok. –

+2

Nella mia esperienza (lavorando su alcune applicazioni Java 250k + LOC relativamente grandi) la riflessione tende a rendere il codice più difficile da capire e mantenere e dovrebbe essere ben pensato prima di introdurlo (ignorando possibili problemi di prestazioni, che possono essere facilmente risolti in un secondo caso da caso base qui comunque). Non riesco a ricordare un singolo errore dovuto al fatto che ho dimenticato di adattare il metodo degli uguali quando si modifica lo stato di una funzione di dati. Tali classi di dati tendono ad essere ragionevolmente brevi e semplici, quindi questo è facilmente catturato almeno nelle revisioni del codice. – Voo

+0

Grazie per la risposta dettagliata. Sono andato con l'opzione numero 1 per ora, ma mi piace molto il # 3 come hai detto tu. # 1 è sufficiente per ora ma se mai dovessi arrivare al punto in cui è una seccatura generare nuovo codice quando cambiano le classi, userò solo # 3. – Guy

3

In genere è possibile generare metodi equals/hashCode dal proprio IDE - tutti i grandi player in questo campo sono in grado di farlo (Eclipse, IntelliJ Idea e Netbeans).

In genere è possibile creare codice che utilizzerà il riflesso ma non lo consiglio come approccio obiettivo più chiaro e più gestibile. Anche la riflessione non sarà veloce come quella "standard". Se si vuole veramente andare in questo modo, esistono utilità come EqualsBuilder e HashCodeBuilder.

Giusto per vostra informazione, esistono linguaggi basati su JVM che già supportano queste funzionalità, ad es. Kotlin data classes, che può essere usato molto bene nei progetti Java esistenti.

4

è possibile utilizzare:

org.apache.commons.lang.builder.CompareToBuilder.reflectionCompare(Object lhs, Object rhs); 

si utilizza la reflection per confrontare i Campi ecco il javadoc: javadoc

+1

Juste prestare attenzione all'utilizzo di refelction perché questo tipo di operazione sarà sempre più lento di eseguire direttamente la stessa operazione. Per maggiori dettagli guarda qui: http://www.ibm.com/developerworks/library/j-dyn0603 (paragrafo sulle prestazioni di Reflection) – hic1086

1

In teoria si potrebbe usare la reflection per creare una sorta di util, come molte persone si suggeriscono nei commenti . Personalmente non ti consiglio di farlo. finirai con qualcosa che funziona parzialmente.

Molte cose in Java si basano su equal o hashCode, ad esempio il metodo contains che potete trovare in qualsiasi cosa che implementa Collection.

Ignora equal (e hashCode) è la soluzione consigliata. Inoltre, penso che qualsiasi IDE decente avrà un'opzione per generarli per te. Quindi puoi farlo più rapidamente rispetto all'utilizzo del riflesso.

0

Questo è il modo lo farei:

@Override 
public boolean equals(Object obj) { 
    if (obj instanceof Car) { 
     return internalEquals((Car) obj); 
    } 
    return super.equals(obj); 
} 

protected boolean internalEquals(Car other) { 
    if(this==other){ 
     return true; 
    } 
    if (other != null) { 
     //suppose fuelType can be Integer. 
     if (this.getFuelType() !=null) { 
      if (other.getFuelType() == null) { 
       return false; 
      } else if (!this.getFuelType().equals(other.getFuelType())) { 
       return false; 
      } 
     } else if(other.getFuelType()!=null){ 
      return false; 
     } 
     if (this.getName() != null) { 
      if (other.getName() == null) { 
       return false; 
      } else if (!this.getName().equals(other.getName())) { 
       return false; 
      } 
     } 
     else if(other.getName()!=null){ 
      return false; 
     } 
     if (this.getDate() != null) { 
      if (other.getDate() == null) { 
       return false; 
      } else if (!this.getDate().getTime()!=(other.getDate().getTime())) { 
       return false; 
      } 
     } 
     else if(other.getDate()!=null){ 
      return false; 
     } 
     return true; 
    } else { 
     return false; 
    } 
} 

EDIT
versione semplificata

 public class Utils{ 
      /** 
      * Compares the two given objects and returns true, 
      * if they are equal and false, if they are not. 
      * @param a one of the two objects to compare 
      * @param b the other one of the two objects to compare 
      * @return if the two given lists are equal. 
      */ 
      public static boolean areObjectsEqual(Object a, Object b) { 

       if (a == b){ 
        return true; 
       } 
       return (a!=null && a.equals(b)); 
      } 

      public static boolean areDatesEqual(Date a, Date b){ 
      if(a == b){ 
       return true; 
      } 
      if(a==null || b==null){ 
       return false; 
      } 
      return a.getTime() == b.getTime(); 
      } 
    } 

    @Override 
    public boolean equals(other obj) { 
     if(this == other){ 
     return true; 
     } 
     if(other == null){ 
      return false; 
     } 
     if (other instanceof Car) { 
      return internalEquals((Car) other); 
     } 
     return super.equals(obj); 
    } 

    protected boolean internalEquals(Car other) {   
     //suppose fuelType can be Integer. 
     if (!Utils.areObjectsEqual(this.getName(), other.getName()){     
      return false; 
     } 
     if (!Utils.areObjectsEqual(this.getName(), other.getName()){ 
      return false; 
     } 
     if (!Utils.areDatesEqual(this.getDate(), other.getDate()){ 
      return false; 
     } 
     return true; 
    } 
} 

Inoltre, non dimenticare su codice hash, che il codice di pari passo.

+0

Il primo 'equals' deve gestire l'essere chiamato con null come argomento, quindi metà dei controlli null non sono necessari. In secondo luogo, consiglierei una semplice classe equalsHelper che gestisce il caso in cui la proprietà può essere nullo per semplificare enormemente il codice. Non è nient'altro che 'o1 == o2 || (o1! = null && o1.equals (o2) 'e semplifica enormemente il tuo codice Modifica: è anche predefinita in Java7 [vedi qui] (http://docs.oracle.com/javase/7/docs/api/ java/util/Objects.html # equals% 28java.lang.Object,% 20java.lang.Object% 29). – Voo

+0

sì, questo è quello che ho nella mia app (classe di utilità), ma voleva un po 'di uguale per la sua roba. Inoltre, riguardo a null, all'inizio degli equivoci forse puoi gestire i controlli sugli oggetti null. Per quanto riguarda il resto, conosco la parte null, volevo solo che lui vedesse come funziona. –

+0

Onestamente quella quantità enorme di codice rispetto a ciò che è probabilmente è proprio intenzione di spaventarlo - mi farebbe sicuramente paura se dovessi scrivere cose del genere. – Voo

3

Prenderò l'opinione dissenziente alla maggioranza (usa apache commons con reflection) qui: Sì, questo è un bit di codice che devi scrivere (lascia che il tuo IDE generi veramente), ma devi farlo una volta sola e il numero di classi di dati che devono implementare equals/hashcode è generalmente piuttosto gestibile - almeno in tutti i grandi progetti (250k + LOC) su cui ho lavorato.

Sicuramente se si aggiunge un nuovo membro alla classe, è necessario ricordare di aggiornare le funzioni equals/hashcode, ma è generalmente facile notare, al più tardi durante le revisioni del codice.

E onestamente se si utilizza una classe di helper semplice che è anche in Java7, è possibile ridurre il codice che Wana Ant ha mostrato immensamente. Davvero tutto ciò che serve è:

@Override 
public boolean equals(Object o) { 
    if (o instanceof Car) { // nb: broken if car is not final - other topic 
     Car other = (Car) o; 
     return Objects.equals(fuelType, other.fuelType) && 
       Objects.equals(made, other.made) && 
       Objects.equals(name, other.name); 
    } 
    return false; 
} 

simile per hashcode:

@Override 
public int hashCode() { 
    return Objects.hash(fuelType, made, name); 
} 

Non più breve come la soluzione di riflessione? Vero, ma è semplice, facile da gestire, adattare e leggere - e le prestazioni sono di ordini di grandezza migliori (che per le classi che implementano uguali e hashcode è spesso importante)

2

Mi limito a inserire una spina per la mia soluzione preferita a questo problema: @AutoValue.

Questo è un progetto open-source di Google che fornisce un processore di annotazione che genera una classe sintetica che implementa equals e hashCode per te.

Poiché è un codice generato automaticamente, non è necessario preoccuparsi di dimenticare accidentalmente un campo o di incasinare l'implementazione equals o hashCode. Ma dal momento che il codice viene generato in fase di compilazione, non vi è sovraccarico di runtime pari a zero (a differenza delle soluzioni basate sulla riflessione). È anche "API invisibile": gli utenti della tua classe non sono in grado di distinguere tra un tipo @AutoValue e un tipo che hai implementato tu stesso, e puoi cambiare avanti e indietro in futuro senza rompere i chiamanti.

Vedere anche this presentation che spiega la logica e fa un lavoro migliore confrontandolo con altri approcci.

Problemi correlati