Apache Commons equals / hashCode builder [закрыто]


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

8 150

8 ответов:

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

вот пример Боба:

public class Bean{

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

}

здесь equals () и hashCode () реализованы с помощью 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 или выше (вдохновленный Guava):

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

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

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

Итак: да, строители commons lang очень предпочтительны по сравнению с вручную построенными equals() и hashCode() методы (или эти ужасные монстры Eclipse будут генерировать для вас), но версии Java 7+ / Guava еще лучше.

и заметка о Hibernate:

будьте осторожны с использованием ленивых коллекций в ваших реализациях equals(), hashCode() и toString (). Это потерпит неудачу с треском, если у вас нет открытого Сессия.


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

a) в обеих версиях 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

b) в зависимости от вашей интерпретации контракта equals (), вы также можете изменить строку(ы)

    if(obj instanceof Bean){

до

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

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

правильный способ включения суперкласса в объекты Guava.реализация hashcode ()?

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


примечание (по мотивам комментария kayahr)

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

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

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

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

вы получите "родной" код, который вы можете настроить, как вам нравится и что вы обязательно поддержка изменений.


пример (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;
    }

}

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

  • значением null
  • создания экземпляра

EqualsBuilder и HashCodeBuilder облегчают сравнение полей, которые могут быть null. С вручную написанным кодом это создает много шаблонных.

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

для Hibernate реализация equals и hashCode не имеет никакого значения. Они просто деталь реализации. для почти всех объектов домена, загруженных в спящий режим, накладные расходы во время выполнения (даже без анализа escape) построителя можно игнорировать. База данных и коммуникационные издержки будут значительными.

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

Если вы не пишете свои собственные, есть также возможность использовать google guava (ранее Google collections)

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

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

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

их два основных сценария для использования hashCode () и equals () являются:

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

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

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() a hashCode () на всех реквизитах, что у нас есть?

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

Итак, для 99% проекта, который я делаю, мы используем следующую реализацию equals () и hashCode (), написанную один раз в базовом классе сущностей, что согласуется с концепциями 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 (я никогда не использую составные ключи).

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

помощник хэш-кода:

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-это максимальное разумное количество свойств в домене модель, если у вас есть больше, вы должны подумать о рефакторинге и введении большего класса вместо того, чтобы поддерживать кучу строк и примитивов.

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