2011-02-18 2 views
149

Мне интересно знать, что люди здесь думают об использовании org.apache.commons.lang.builderEqualsBuilder/HashCodeBuilder для реализации equals/hashCode? Будет ли это лучше, чем писать ваши собственные? Он хорошо играет с Hibernate? Каково ваше мнение?Apache Commons равен/Hashcode строитель

+16

Просто не искушайтесь функциями 'reflectionEquals' и' reflectionHashcode'; производительность является абсолютным убийцей. – skaffman

+13

Я видел некоторое обсуждение здесь о равных вчера и имел некоторое свободное время, поэтому я сделал быстрый тест. У меня было 4 объекта с разными реализациями равных. eclipse generated, equalsbuilder.append, equalsbuilder.reflection и pojomatic аннотации. Базой было затмение. equalsbuilder.append занял 3.7x. pojomatic взял 5 раз. на основе отражения взято 25.8x. Это было довольно обескураживающе, потому что мне нравилась простота отражения, и я не могу вынести имя «pojomatic». – digitaljoel

+5

Другим вариантом является Project Lombok; он использует генерацию байт-кода вместо отражения, поэтому он должен работать так же, как и Eclipse-сгенерированный. http://projectlombok.org/features/EqualsAndHashCode.html – Miles

ответ

203

Создатели сообщества/lang великолепны, и я использую их в течение многих лет без заметных эксплуатационных издержек (с и без спящего режима). Но, как пишет Ален, как гуавы еще лучше:

Вот пример Bean:

public class Bean{ 

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

} 

Вот равно() и хэш-код() реализован с 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; 
    } 
} 

и здесь с Java 7 или выше (вдохновленный гуавы):

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

Примечание: этот код изначально ссылается гуавы, бушель t, как отмечали в комментариях, эта функциональность с тех пор была внедрена в JDK, поэтому Guava больше не требуется.

Как вы можете видеть, версия Guava/JDK короче и позволяет избежать лишних вспомогательных объектов. В случае равных, он даже допускает короткое замыкание оценки, если предыдущий вызов Object.equals() возвращает false (справедливо: commons/lang имеет метод ObjectUtils.equals(obj1, obj2) с идентичной семантикой, который может использоваться вместо EqualsBuilder, чтобы обеспечить короткое замыкание, как указано выше).

Итак: да, строители Commons Lang очень предпочтительнее вручную построенных equals() и hashCode() методов (или тех ужасных монстров Затмение будет генерировать для вас), но версии Java 7+/гуавы даже лучше.

И записка о Hibernate:

быть осторожны при использовании ленивых коллекции в ваших Equals(), хэш-код() и ToString (реализации). Это будет неудачно, если у вас нет открытого сеанса.


Примечание (о равных()):

а) в обеих версиях Equals() выше, вы можете использовать один или оба из этих ярлыков также:

@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 

б) в зависимости от вашей интерпретации равенства() контракта, вы можете также изменить линию (ы)

if(obj instanceof Bean){ 

к

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

Если вы используете второй вариант, вы, вероятно, также хотите вызвать super(equals()) внутри метода equals(). Мнения здесь различаются, тема обсуждается в этом вопросе:

right way to incorporate superclass into a Guava Objects.hashcode() implementation?

(хотя речь идет о hashCode(), то же самое относится и к equals())


Примечание (вдохновленный комментарий от kayahr)

Objects.hashCode(..) (так же, как и базовый Arrays.hashCode(...)) может быть неудачным, если у вас много примитивных полей. В таких случаях EqualsBuilder может быть лучшим решением.

+34

То же самое можно сделать с Java 7 Objects.equals: http://download.oracle.com/javase/7/docs/api/java/util/Objects.html#equals%28java.lang.Object,%20java. lang.Object% 29 –

+0

@ Томас хороший, это шаг в правильном направлении! –

+0

Мне нравятся функции commons-lang, но разве не плохая практика создавать новый объект для каждого вызова .hashCode() или .equals()? – Stephan

6

EqualsBuilder и HashCodeBuilder имеют два основных аспекта, которые отличаются от вручную написанного кода:

  • нулевой обработки
  • создания экземпляра

EqualsBuilder и HashCodeBuilder сделать его легче сравнивать поля, Co uld be null. При ручном написании кода это создает много шаблонов.

С другой стороны, EqualsBuilder создает экземпляр для вызова метода equals. Если ваши методы equals часто звонят, это создаст много примеров.

Для Hibernate реализация equals и hashCode не имеет значения. Это всего лишь деталь реализации. Для почти всех объектов домена, загруженных спящим режимом, служебные данные времени выполнения (даже без анализа escape-кода) Builder могут быть проигнорированы. Накладные расходы на базу данных и связь будут значительными.

Как сказал скаффман, версия отражения не может использоваться в производственном коде. Отражение будет замедляться, и «реализация» будет неправильной для всех, кроме простейших классов. Учет всех членов также опасен, поскольку вновь введенные члены меняют поведение метода равных. Версия отражения может быть полезна в тестовом коде.

+0

Я не согласен с тем, что реализация отражения «не будет правильной для всех, кроме простейших классов». С помощью разработчиков вы можете явно исключать поля, если хотите, так что реализация действительно зависит от определения вашего бизнес-ключа. К сожалению, я не могу не согласиться с аспектом производительности реализации на основе отражения. – digitaljoel

+1

@digitaljoel Да, вы можете исключить поля, но эти определения не являются рефакторингом save. Поэтому я не упомянул их специально. –

+0

Хорошая точка Томас. Благодарю. – digitaljoel

8

Если вы не хотите зависеть от сторонней библиотеки (возможно, у вас работает устройство с ограниченными ресурсами), и вы даже не хотите вводить свои собственные методы, вы также можете позволить IDE выполнять работу, например в использовании затмение

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

вы получите «родной» код, который вы можете Configure, как вам нравится, и который вы должны поддержки на изменения.


Пример (затмение 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; 
    } 

} 
+13

Правда, но код, созданный Eclipse, нечитабелен и не поддается контролю. –

+6

Пожалуйста, никогда не думайте о чем-то столь же ужасном, как и «затмение», созданное затмением. Если вы не хотите зависеть от сторонней библиотеки, напишите однострочный метод, например 'Objects.equal'. Даже когда используется только один или два раза, это делает код намного лучше! – maaartinus

+0

@maaartinus 'equals' /' hashCode' одна линия методов ??? – FrVaBe

0

Если вы просто имеем дело с бина сущности, где идентификатор является первичным ключом, вы можете упростить.

@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

По моему мнению, это не очень хорошо сочетается с Hibernate, особенно примеры из ответа, сравнивающие длину, имя и дочерние элементы для некоторого объекта. Hibernate рекомендует to use business key для использования в equals() и hashCode(), и у них есть свои причины. Если вы используете генераторы auto equals() и hashCode() для вашего бизнес-ключа, все в порядке, только проблемы с производительностью необходимо учитывать, как упоминалось ранее. Но люди обычно используют все свойства, что ИМО ошибочно. Например, я сейчас работаю над проектом, в котором объекты написаны с использованием Pojomatic с @AutoProperty, что я считаю очень плохой моделью.

Их два основных сценария использования хэш-код() и Equals() являются:

  • когда вы кладете экземпляры стойких классов в наборе (в рекомендуемый способ представления многозначные ассоциации) и
  • при использовании воссоединения отдельных экземпляров

Итак, давайте предположим, наша сущность выглядит следующим образом:

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

Оба являются одним и тем же объектом для Hibernate, которые были извлечены из некоторого сеанса в какой-то момент (их идентификатор и класс/таблица равны). Но когда мы реализуем auto equals() hashCode() на всех реквизитах, что у нас есть?

  1. Когда вы помещаете сущность2 в постоянный набор, где сущность1 уже существует, это будет помещено дважды и приведет к исключению во время фиксации.
  2. Если вы хотите присоединить отсоединенный объект2 к сеансу, где сущность1 уже существует, они (вероятно, я не тестировал это особенно) не будут слиты должным образом.

Так, 99% проекта я делаю, мы используем следующую реализацию равных() и хэш-код() написанную один раз в базовом классе сущностей, что согласуется с концепциями 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(); 
} 

Для переходного объекта я делаю то же самое, что Hibernate будет делать на шаге упорства, т.е. Я использую совпадение экземпляра. Для постоянных объектов я сравниваю уникальный ключ, который является таблицей/id (я никогда не использую составные клавиши).

0

На всякий случай, другие сочтут это полезным, я придумал этот класс Helper для вычисления хеш-кода, который позволяет избежать дополнительных накладных расходов на создание объектов, упомянутых выше (на самом деле служебные данные метода Objects.hash() - это даже больше, если у вас есть наследование, поскольку оно создаст новый массив на каждом уровне!). Пример

Использование:

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

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

Помощник 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); 
    } 
} 

Я понял, что 10 является максимально разумное число свойств в модели предметной области, если у вас есть больше, вы должны думать рефакторинга и введения большего количества классов вместо сохранения кучи строк и примитивов.

Недостатки: это не полезно, если у вас есть в основном примитивы и/или массивы, которые вам нужны для хэширования.(Обычно это так, когда вам приходится иметь дело с плоскими (переносными) объектами, которые находятся вне вашего контроля).

14

Люди, проснись! С Java 7 есть вспомогательные методы для equals и hashCode в стандартной библиотеке. Их использование полностью эквивалентно использованию методов Guava.

+0

a) в то время, когда задавался этот вопрос, Java 7 wasn там еще нет б) технически, они не совсем эквивалентны. jdk имеет метод Objects.equals и методы ObjectAsequal Guava. Я могу использовать статический импорт только с версией Guava. Это просто косметика, я знаю, но это делает негуаву значительно более загроможденным. –

+0

Это не является хорошим методом для переопределения объектов равным методу из-за того, что Objects.equals вызовет метод .equals экземпляра. Если вы вызовете Objects.equals в методе .equals экземпляра, это приведет к переполнению стека. – dardo

+0

Можете ли вы привести пример, когда он попадает в петлю? –