2015-10-13 15 views
19

Mam klasy Java, oto przykład:Sprawdź, czy dwa obiekty są całkowicie równe w Javie

public class Car { 

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

Teraz powiedzmy, że mam dwa obiekty samochód i chcę porównać czy wszystkie ich zmienne są równe .

W tej chwili rozwiązałem to przez nadpisanie metody equals(Object o) i sprawdzam, czy wszystkie zmienne pasują do obu obiektów.

Problem polega na tym, że jeśli mam 20 klas, będę musiał zastąpić equals(Object o) w każdym z nich.

Czy istnieje sposób stworzenia jakiejś uniwersalnej metody, która mogłaby porównać dowolny z dwóch obiektów, które mu przekazuję i dać mi znać, czy pasują do każdej zmiennej, czy nie?

+4

jeśli za pomocą Eclipse kliknij prawym przyciskiem myszy -> źródło-> generuj równe i hash. wygenerowane równości będą porównywane przy użyciu dowolnych pól, na które je ustawisz. nawiasem mówiąc, tak, to jest standardowa metoda "Object.equals (Object other)". – eduyayo

+5

Yup. Możesz stworzyć pomocnika, który używa refleksji i kończy na kruchym kodzie, który jest po prostu * związany *, aby stworzyć problemy w pewnym momencie. Możesz też raz go zasysać i pracować w oknach 20 generacji oferowanych przez IDE. –

+1

To najprostszy sposób, możesz także użyć refleksji: http://stackoverflow.com/questions/1449001/is-there-a-java-reflection-utility-to-do-a-deep-comparison-of-two- obiekty –

Odpowiedz

20

masz kilka opcji do automatyzacji Równa & Kod źródłowy (opcja # 3 wiał mój umysł !):

  1. Twoje IDE. Nie polecałbym go dla większości obiektów, ponieważ mogą one powoli dryfować poza aktualną definicją klasy. Wyglądają też brzydko i zanieczyszczają twój kod bazowy za pomocą kodu standardowego.
  2. Apache Commons ma wiele rzeczy ułatwiających to zadanie, w tym reflective version, więc nie ma ryzyka, że ​​wydostanie się z zajęć jest nieaktualne. Jest lepsza niż # 1, chyba że potrzebujesz szybkiego equals/hashcode, ale nadal zbyt wiele boombox dla mojego upodobania.
  3. Project Lombok i przetwarzanie adnotacji. Pobij adnotację EqualsAndHashCode w klasie ya i skończ z nią. Polecam przy użyciu Project Lombok. Dodaje odrobinę magii do kompilacji (ale niewiele), a więc wymaga wtyczki, aby twój IDE ładnie się zachowywał, ale jest to niewielka cena, którą można zapłacić za brak kodu standardowego. Lombok jest procesorem adnotacji, który działa w czasie kompilacji, więc nie masz żadnego działania wydajności środowiska wykonawczego.
  4. Używanie innego języka, który obsługuje go poza polem, ale jest także celem JVM. Groovy używa annotation i Kotlin obsługuje data classes. Dopóki twój istniejący kod nie zostanie szybko przekonwertowany, uniknęłbym tego.
  5. ma AutoValue.Jak Projektu Lombok jest to procesor adnotacja, jednak ma mniejszą magię kosztem trochę więcej boilerplate (dzięki Louis Wasserman)
+1

Alternatywnie, jeśli nie lubisz magicznie generowanych metod, https://github.com/google/auto/ tree/master/value osiąga podobne cele do Lombok z mniejszą magią. [Ta sekcja] (https://github.com/google/auto/tree/master/value#alternatives) omawia niektóre różnice z Lombok. –

+2

Z mojego doświadczenia (praca nad stosunkowo dużymi aplikacjami Java na poziomie 250k + LOC) odbicie sprawia, że ​​kod jest trudniejszy do zrozumienia i utrzymania i powinien być dobrze przemyślany przed jego wprowadzeniem (ignorując możliwe problemy z wydajnością, które mogą być później łatwo rozwiązane przez tu na wszelki wypadek). Nie pamiętam pojedynczego błędu z powodu zapomnienia dostosowania metody równości przy zmianie stanu funkcji danych. Takie klasy danych wydają się być rozsądnie krótkie i proste, więc łatwo można je złapać przynajmniej w przeglądach kodu. – Voo

+0

Dzięki za szczegółową odpowiedź. Poszedłem z opcją nr 1 na razie, ale naprawdę uwielbiam numer 3, tak jak powiedziałeś. # 1 jest na razie wystarczający, ale jeśli kiedykolwiek dojdę do punktu, w którym jest problem z wygenerowaniem nowego kodu, gdy klasy się zmienią, użyję tylko # 3. – Guy

3

Zazwyczaj można generować metody equals/hashCode za pomocą IDE - wszystkie duże odtwarzacze w tym polu są w stanie to zrobić (Eclipse, IntelliJ Idea i Netbeans).

Generalnie można utworzyć kod, który użyje refleksji, ale nie polecam tego, ponieważ podejście obiektywne jest bardziej przejrzyste i łatwiejsze w utrzymaniu. Również refleksja nie będzie tak szybka jak "standardowa" droga. Jeśli naprawdę chcesz iść w ten sposób, istnieją narzędzia takie jak EqualsBuilder i HashCodeBuilder.

Dla potrzeb informacji dostępne są języki oparte na JVM, które obsługują już te funkcje, np. Kotlin data classes, który może być całkiem dobrze wykorzystany w istniejących projektach Java.

4

można użyć:

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

wykorzystuje odbicia porównać fileds oto javadoc: javadoc

+1

Juste zwracają uwagę na stosowanie refelction, ponieważ ten typ operacji zawsze będzie wolniejszy niż po prostu wykonanie tej samej operacji bezpośrednio. Aby uzyskać więcej informacji, zajrzyj tutaj: http://www.ibm.com/developerworks/library/j-dyn0603 (akapit dotyczący wydajności refleksji) – hic1086

1

Teoretycznie można użyć refleksji stworzyć jakąś util, jak wiele osób proponujemy w komentarzach . Osobiście nie polecam ci tego. skończysz z czymś, co częściowo działa.

Wiele rzeczy w Javie polegać na equal lub hashCode, na przykład metodą contains które można znaleźć w cokolwiek, który implementuje Collection.

Zastępowanie equal (i hashCode) jest zalecanym rozwiązaniem. Dodatkowo, myślę, że każdy przyzwoity IDE będzie miał opcję wygenerowania ich dla ciebie. Dlatego możesz zrobić to szybciej, niż poprzez użycie refleksji.

0

To jest sposób by to zrobić:

@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
Uproszczona wersja

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

także nie zapomnieć o hashcode, że kod w parze.

+0

Pierwszy 'równy' musi obsłużyć wywołanie z argumentem null jako argumentem, więc połowa sprawdzeń zerowych jest niepotrzebna. Po drugie, polecam prostą klasę equalsHelper, która obsługuje przypadek, gdy własność własna może mieć wartość null, co znacznie upraszcza kod. To nic innego jak "o1 == o2 || (o1! = null && o1.equals (o2) 'i znacznie upraszcza twój kod Edit: To jest nawet domyślnie w Java7 [zobacz tutaj] (http://docs.oracle.com/javase/7/docs/api/ java/util/Objects.html # equals% 28java.lang.Object,% 20java.lang.Object% 29). – Voo

+0

tak, to jest to, co mam w mojej aplikacji (klasa użytkowa), ale chciał czegoś równego dla swoich rzeczy. Ponadto, w odniesieniu do wartości zerowej, na początku równości może być w stanie obsłużyć zerowe sprawdzanie obiektów. Jeśli chodzi o resztę, znam część zerową, chciałem tylko, żeby zobaczył, jak to działa. –

+0

Szczerze mówiąc, olbrzymia ilość kodu w porównaniu do tego, co jest tak naprawdę to go tylko przestraszy - na pewno przestraszyłbym się, gdybym musiał pisać takie rzeczy. – Voo

3

Przyjmę zdanie odrębne do większości (użyj apache commons z odbiciem) tutaj: Tak, jest to kod, który musisz napisać (pozwól wygenerować IDE naprawdę), ale musisz to zrobić tylko raz a liczba klas danych, które wymagają implementacji equals/hashcode, jest generalnie zarządzalna - przynajmniej we wszystkich dużych projektach (250k + LOC), nad którymi pracowałem.

Oczywiście, jeśli dodasz nowego członka do klasy, będziesz musiał pamiętać o aktualizacji funkcji equals/hashcode, ale generalnie łatwo to zauważyć, najpóźniej podczas recenzji kodu.

Szczerze mówiąc, jeśli używasz prostej, małej klasy pomocniczej, która jest nawet w Javie7, możesz ograniczyć kod, który Wana Ant pokazał niezmiernie. Naprawdę wszystko czego potrzebujesz to:

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

podobny do hashcode:

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

Nie tak krótkie jak roztworze refleksji? To prawda, ale jest prosta, łatwa w utrzymaniu, adaptacji i czytaniu - a wydajność jest o kilka rzędów wielkości lepsza (co dla klas, które implementują equals i hashcode jest często ważne)

2

Po prostu wrzucę wtyczkę do mojego ulubionego rozwiązania do tego problemu: @AutoValue.

Jest to projekt typu open-source firmy Google, który udostępnia procesor adnotacji, który generuje klasę syntetyczną, która implementuje dla Ciebie equals i hashCode.

Ponieważ jest to kod wygenerowany automatycznie, nie musisz martwić się o przypadkowe zapomnienie pola lub zepsucie implementacji equals lub hashCode. Ale ponieważ kod jest generowany w czasie kompilacji, nie ma narzutu na środowisko wykonawcze (w przeciwieństwie do rozwiązań opartych na odbiciach). Jest także "niewidoczny dla API" - użytkownicy Twojej klasy nie mogą odróżnić typu @AutoValue od typu zaimplementowanego samodzielnie, a Ty możesz zmieniać w przyszłości tam iz powrotem bez rozbijania rozmówców.

Zobacz także this presentation, który wyjaśnia uzasadnienie i lepiej sprawdza się w porównaniu z innymi podejściami.

Powiązane problemy