Мне любопытно узнать, что люди здесь думают об использовании
org.apache.commons.lang.builder EqualsBuilder/HashCodeBuilder
для реализации equals/hashCode? Будет ли это лучше, чем писать ваши собственные? Он хорошо играет с Hibernate? Что вы думаете?
Apache Commons equals/hashCode builder
Ответ 1
Создатели commons/lang великолепны, и я использую их в течение многих лет без заметных издержек производительности (с и без спящего режима). Но, как пишет Ален, путь Гува еще приятнее:
Здесь пример Bean:
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;
    }
}
и здесь с Guava:
@Override
public int hashCode(){
    return Objects.hashCode(name, length, children);
}
@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equal(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equal(children, other.children);
    } else{
        return false;
    }
}
Как вы видите, версия Guava короче и позволяет избежать лишних вспомогательных объектов. В случае равных, это даже допускает короткое замыкание оценки, если предыдущий вызов Object.equal() возвращает false (справедливо: commons/lang имеет метод ObjectUtils.equals(obj1, obj2) с идентичной семантикой, который можно использовать вместо EqualsBuilder для позвольте короткому замыканию, как указано выше).
Итак: да, разработчики commons lang очень предпочтительнее созданных вручную методов equals() и hashCode() (или тех ужасных монстров, которые Eclipse будет генерировать для вас), но версии Guava еще лучше.
И записка о Hibernate:
будьте осторожны с использованием ленивых коллекций в реализациях equals(), hashCode() и toString(). Это будет неудачно, если у вас нет открытого сеанса.
Примечание (около equals()):
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 Objects.hashcode()
(хотя это примерно hashCode(), то же самое относится к equals())
Примечание (вдохновлено комментарием kayahr)
 Objects.hashCode(..) (так же, как базовый Arrays.hashCode(...)) может работать плохо, если у вас много примитивных полей. В таких случаях EqualsBuilder может быть лучшим решением.
Ответ 2
Люди, проснись! Так как Java 7 существуют вспомогательные методы для equals и hashCode в стандартная библиотека. Их использование полностью эквивалентно использованию методов Guava.
Ответ 3
Если вы не хотите зависеть от сторонней библиотеки (возможно, у вас запущено устройство с ограниченными ресурсами), и даже вы не хотите вводить свои собственные методы, вы также можете позволить IDE выполнять задание, например. в использовании eclipse
Source -> Generate hashCode() and equals()...
Вы получите "собственный" код, который вы можете настроить по своему усмотрению и который необходимо поддерживать при изменениях.
Пример (затмение 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;
    }
}
Ответ 4
У EqualsBuilder и HashCodeBuilder есть два основных аспекта, которые отличаются от написанного вручную кода:
- null обработка
- создание экземпляра
EqualsBuilder и HashCodeBuilder упрощают сравнение полей, которые могут быть нулевыми. При ручном написании кода это создает много шаблонов.
EqualsBuilder, с другой стороны, создаст вызов экземпляра за один равный метод. Если ваши методы equals вызываются часто, это создаст много экземпляров.
Для Hibernate реализация equals и hashCode не имеет значения. Это всего лишь деталь реализации. Для почти всех объектов домена, загруженных спящим режимом, служебные данные времени выполнения (даже без анализа escape) Builder можно игнорировать. База данных и коммуникационные издержки будут значительными.
Как сказал скаффман, версия отражения не может использоваться в производственном коде. Отражение будет замедляться, и "реализация" будет неправильной для всех, кроме простейших классов. Учет всех членов также опасен, поскольку вновь введенные члены меняют поведение метода равных. Версия отражения может быть полезна в тестовом коде.
Ответ 5
Если вы не хотите писать свои собственные, есть возможность использовать google guava (ранее коллекции google)
Ответ 6
Если вы просто имеете дело с сущностью 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();
   }
Ответ 7
По-моему, это не очень хорошо сочетается с Hibernate, особенно примеры из ответа, сравнивающего длину, имя и дочерние элементы для некоторого объекта. Hibernate советует использовать бизнес-ключ для использования в equals() и hashCode(), и у них есть свои причины. Если вы используете генераторы auto equals() и hashCode() на своем бизнес-ключе, это нормально, только проблемы с производительностью необходимо учитывать, как упоминалось ранее. Но люди обычно используют все свойства, что ИМО ошибочно. Например, я в настоящее время работает над проектом, где объекты написаны с использованием Pojomatic с @AutoProperty, что я считаю очень неудачным.
Их два основных сценария использования hashCode() и equals():
- когда вы помещаете экземпляры постоянных классов в Set ( рекомендуемый способ представления многозначных ассоциаций) и
- при повторном подключении отдельных экземпляров
Итак, предположим, что наш объект выглядит следующим образом:
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, которые были извлечены из некоторого сеанса в какой-то момент (их id и class/table равны). Но когда мы реализуем auto equals() hashCode() на всех реквизитах, что у нас есть?
- Когда вы помещаете сущность2 в постоянный набор, где сущность1 уже существует, это будет помещено дважды и приведет к исключению во время фиксации.
- Если вы хотите присоединить выделенный элемент2 к сеансу, где сущность1 уже существует, они (вероятно, я не тестировал это особенно) не будут правильно объединены.
Итак, для 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 (я никогда не использую составные клавиши).
Ответ 8
На всякий случай, другие найдут это полезным, я придумал этот класс 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 - это максимально допустимое количество свойств в модели домена, если у вас есть больше, вы должны думать о рефакторинге и вводить больше классов вместо сохранения кучи строк и примитивов.
Недостатки: это не полезно, если у вас есть в основном примитивы и/или массивы, которые вам нужно глубоко использовать. (Обычно это так, когда вам приходится иметь дело с плоскими (переносными) объектами, которые находятся вне вашего контроля).
