Apache Commons równa się / hashCode builder [zamknięty]

Jestem ciekaw, co ludzie tutaj myślą o użyciu org.apache.commons.lang.builder EqualsBuilder/HashCodeBuilder do realizacji equals/hashCode? Czy byłaby to lepsza praktyka niż pisanie własnych? Czy dobrze gra z Hibernate? Jakie jest twoje zdanie?

Author: palacsint, 2011-02-18

8 answers

Budowniczowie commons / lang są świetni i używam ich od lat bez zauważalnej wydajności (z hibernate i bez). Ale jak pisze Alain, sposób Guava jest jeszcze ładniejszy: {]}

Oto przykładowa fasolka:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

Oto equals() i hashCode () zaimplementowane z Commons/Lang:

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

A tu z Javą 7 lub wyższą (inspirowaną Guawą):

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

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

Uwaga: Ten kod pierwotnie odnosił się do guawy, ale jak zauważyły Komentarze, to funkcjonalność została wprowadzona w JDK, więc Guava nie jest już wymagana.

Jak widać Wersja Guava / JDK jest krótsza i pozwala uniknąć zbędnych obiektów pomocniczych. W przypadku równości pozwala nawet na zwarcie ewaluacji, jeśli wcześniejsze wywołanie Object.equals() zwróci false (szczerze mówiąc: commons / lang ma metodę ObjectUtils.equals(obj1, obj2) o identycznej semantyce, która może być użyta zamiast EqualsBuilder, aby umożliwić zwarcie jak wyżej).

Więc: tak, Budowniczowie commons są bardzo lepiej niż ręcznie skonstruowane metody equals() i hashCode() (lub te okropne potwory, które Eclipse wygeneruje dla ciebie), ale wersje Java 7+ / Guava są jeszcze lepsze.

I notka o Hibernate:

Uważaj na używanie leniwych kolekcji w implementacjach equals(), hashCode() i toString (). To zawiedzie nędznie, jeśli nie masz otwartej sesji.


Note (about equals()):

A) w obu wersjach equals() powyżej, możesz chcieć użyj również jednego lub obu tych skrótów:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

B) w zależności od twojej interpretacji umowy equals (), możesz również zmienić linię(y)

    if(obj instanceof Bean){

Do

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

Jeśli używasz drugiej wersji, prawdopodobnie chcesz wywołać super(equals()) wewnątrz swojej metody equals(). Opinie różnią się tutaj, Temat jest omawiany w tym pytaniu:

Dobry sposób na włączenie superklasy do obiektów Guava.hashcode() wdrożenie?

(chociaż chodzi o hashCode(), to samo dotyczy equals())


Notka (zainspirowana komentarzem z kayahr)

Objects.hashCode(..) (podobnie jak podstawowe Arrays.hashCode(...)) może działać źle, jeśli masz wiele prymitywnych pól. W takich przypadkach, EqualsBuilder może być rzeczywiście lepszym rozwiązaniem.

 206
Author: Sean Patrick Floyd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-10 19:04:39

Ludzie, obudźcie się! od Java 7 istnieją metody pomocnicze dla równa sięi hashCode w bibliotece standardowej. Ich użycie jest w pełni równoważne z użyciem metod Guava.

 14
Author: Mikhail Golubtsov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-04 07:51:38

Jeśli nie chcesz polegać na bibliotece innej firmy (być może używasz urządzenia a z ograniczonymi zasobami), a nawet nie chcesz wpisywać własnych metod, możesz również pozwolić IDE wykonać to zadanie, np. w eclipse użyj

Source -> Generate hashCode() and equals()...

Otrzymasz 'natywny' kod, który możesz skonfigurować tak, jak chcesz i który musisz wspierać przy zmianach.


Przykład (eclipse Juno):

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}
 8
Author: FrVaBe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-08 20:45:06

EqualsBuilder i HashCodeBuilder mają dwa główne aspekty, które różnią się od ręcznie pisanego kodu:

  • null handling
  • Tworzenie instancji

EqualsBuilder i HashCodeBuilder ułatwiają porównywanie pól, które mogą być null. Przy ręcznym pisaniu kodu tworzy to dużo kotła.

EqualsBuilder z drugiej strony utworzy instancję dla wywołania metody equals. Jeśli twoje metody są często wywoływane, stworzy to wiele przypadków.

Dla Hibernate implementacja equals i hashCode nie ma znaczenia. Są one tylko szczegółem realizacji. dla prawie wszystkich obiektów domeny załadowanych za pomocą hibernate, overhead runtime (nawet bez analizy escape) Buildera może być zignorowany. Baza danych i komunikacja będą znaczące.

Jak wspomniał skaffman, wersja reflection nie może być używana w kodzie produkcyjnym. Refleksja będzie powolna, a "realizacja" nie będzie poprawne dla wszystkich, oprócz najprostszych klas. Uwzględnienie wszystkich członków jest również niebezpieczne, ponieważ nowo wprowadzeni członkowie zmieniają zachowanie metody equals. Wersja reflection może być przydatna w kodzie testowym.

 6
Author: Thomas Jung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-18 08:24:15

Jeśli nie piszesz własnego, istnieje również możliwość skorzystania z Google guava (dawniej Google collections)

 4
Author: Alain Pannetier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-27 22:21:47

Jeśli masz do czynienia tylko z encją, w której id jest kluczem podstawowym, możesz to uprościć.

   @Override
   public boolean equals(Object other)
   {
      if (this == other) { return true; }
      if ((other == null) || (other.getClass() != this.getClass())) { return false; }

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }
 0
Author: DEREK LEE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-22 19:30:23

Moim zdaniem nie gra dobrze z Hibernate, szczególnie przykłady z odpowiedzi porównującej długość, imię i dzieci dla jakiegoś podmiotu. Hibernate radzi aby używać klucza biznesowego do użycia w equals() i hashCode () i mają swoje powody. Jeśli używasz auto equals() i hashCode () generator na klucz biznesowy, to jest w porządku, tylko problemy z wydajnością muszą być brane pod uwagę, jak wspomniano wcześniej. Ale ludzie zwykle używają wszystkich właściwości, co jest IMO bardzo złe. Na przykład obecnie pracuję nad projektem, w którym encje są pisane za pomocą Pojomatic z @AutoProperty, co uważam za naprawdę zły wzór.

Ich dwa główne scenariusze użycia hashCode () i equals() to:

  • gdy umieścisz instancje klas trwałych w zbiorze (the zalecany sposób reprezentowania wielu cenionych stowarzyszeń) i
  • kiedy używasz reattachment odłączonych instancji

Załóżmy więc, że nasz byt wygląda tak:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

Oba są ta sama encja dla Hibernate, która została pobrana z jakiejś sesji w pewnym momencie(ich id i Klasa / Tabela są równe). Ale kiedy zaimplementujemy auto equals () a hashCode() na wszystkich rekwizytach, co mamy?

  1. gdy umieścisz entity2 na trwałym zestawie, w którym entity1 już istnieje, zostanie to wprowadzone dwa razy i spowoduje wyjątek podczas commit.
  2. Jeśli chcesz załączyć oddzielny entity2 do sesji, gdzie entity1 już istnieje (prawdopodobnie, nie testowane to szczególnie) nie zostaną prawidłowo połączone.

Tak więc, dla 99% projektu, który wykonuję, używamy następującej implementacji equals() i hashCode () napisanej raz w podstawowej klasie encji, co jest zgodne z pojęciami Hibernate:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

Dla istoty przejściowej robię to samo, co Hibernate zrobi na kroku persistence, tj. Używam dopasowania instancji. Dla trwałych obiektów porównuję unikalny klucz, którym jest table/id (nigdy nie używam kluczy kompozytowych).

 0
Author: Łukasz Frankowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-22 10:16:09

Na wszelki wypadek, dla innych będzie to przydatne, wymyśliłem tę klasę pomocniczą do obliczeń kodu hashowego, która pozwala uniknąć dodatkowych kosztów tworzenia obiektów wymienionych powyżej (w rzeczywistości, kosztów obiektów.metoda hash () jest jeszcze większa, gdy masz dziedziczenie, ponieważ utworzy nową tablicę na każdym poziomie!).

Przykład użycia:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

Pomocnik HashCode:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

Doszedłem do wniosku, że 10 to maksymalna rozsądna liczba właściwości w modelu domeny, jeśli masz więcej powinieneś pomyśleć o refaktoryzacji i wprowadzeniu większej klasy zamiast utrzymywania sterty ciągów i prymitywów.

Wady są następujące: nie jest to przydatne, jeśli masz głównie prymitywy i / lub tablice, które musisz głęboko hashować. (Zwykle ma to miejsce, gdy masz do czynienia z płaskimi (transferowymi) obiektami, które są poza Twoją kontrolą).

 0
Author: Vlad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 06:55:06