Apache Commons equals / hashCode builder [закрыто]
мне любопытно узнать, что люди здесь думают об использовании
org.apache.commons.lang.builder
EqualsBuilder
/HashCodeBuilder
для реализации equals
/hashCode
? Будет ли это лучше, чем писать свой собственный? Хорошо ли он играет с Hibernate? Каково Ваше мнение?
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
на самом деле может быть лучшим решением.
Если вы не хотите зависеть от сторонней библиотеки (возможно, вы используете устройство с ограниченными ресурсами), и вы даже не хотите вводить свои собственные методы, вы также можете позволить 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 () на всех реквизитах, что у нас есть?
- когда вы помещаете entity2 в постоянный набор, где entity1 уже существует, это будет помещено дважды и приведет к исключению во время фиксации.
- если вы хотите присоединить отсоединенный 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-это максимальное разумное количество свойств в домене модель, если у вас есть больше, вы должны подумать о рефакторинге и введении большего класса вместо того, чтобы поддерживать кучу строк и примитивов.
недостатки: это не полезно, если у вас есть в основном примитивы и/или массивы, которые вам нужно глубоко хэшировать. (Обычно это тот случай, когда вам приходится иметь дело с плоскими (переносными) объектами, которые находятся вне вашего контроля).