Мне интересно знать, что люди здесь думают об использовании org.apache.commons.lang.builder
EqualsBuilder
/HashCodeBuilder
для реализации equals
/hashCode
? Будет ли это лучше, чем писать ваши собственные? Он хорошо играет с Hibernate? Каково ваше мнение?Apache Commons равен/Hashcode строитель
ответ
Создатели сообщества/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
может быть лучшим решением.
То же самое можно сделать с Java 7 Objects.equals: http://download.oracle.com/javase/7/docs/api/java/util/Objects.html#equals%28java.lang.Object,%20java. lang.Object% 29 –
@ Томас хороший, это шаг в правильном направлении! –
Мне нравятся функции commons-lang, но разве не плохая практика создавать новый объект для каждого вызова .hashCode() или .equals()? – Stephan
Если вы не писать свой собственный, есть также возможность использовать google guava (formerly google collections)
EqualsBuilder и HashCodeBuilder имеют два основных аспекта, которые отличаются от вручную написанного кода:
- нулевой обработки
- создания экземпляра
EqualsBuilder и HashCodeBuilder сделать его легче сравнивать поля, Co uld be null. При ручном написании кода это создает много шаблонов.
С другой стороны, EqualsBuilder создает экземпляр для вызова метода equals. Если ваши методы equals часто звонят, это создаст много примеров.
Для Hibernate реализация equals и hashCode не имеет значения. Это всего лишь деталь реализации. Для почти всех объектов домена, загруженных спящим режимом, служебные данные времени выполнения (даже без анализа escape-кода) Builder могут быть проигнорированы. Накладные расходы на базу данных и связь будут значительными.
Как сказал скаффман, версия отражения не может использоваться в производственном коде. Отражение будет замедляться, и «реализация» будет неправильной для всех, кроме простейших классов. Учет всех членов также опасен, поскольку вновь введенные члены меняют поведение метода равных. Версия отражения может быть полезна в тестовом коде.
Я не согласен с тем, что реализация отражения «не будет правильной для всех, кроме простейших классов». С помощью разработчиков вы можете явно исключать поля, если хотите, так что реализация действительно зависит от определения вашего бизнес-ключа. К сожалению, я не могу не согласиться с аспектом производительности реализации на основе отражения. – digitaljoel
@digitaljoel Да, вы можете исключить поля, но эти определения не являются рефакторингом save. Поэтому я не упомянул их специально. –
Хорошая точка Томас. Благодарю. – digitaljoel
Если вы не хотите зависеть от сторонней библиотеки (возможно, у вас работает устройство с ограниченными ресурсами), и вы даже не хотите вводить свои собственные методы, вы также можете позволить 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;
}
}
Правда, но код, созданный Eclipse, нечитабелен и не поддается контролю. –
Пожалуйста, никогда не думайте о чем-то столь же ужасном, как и «затмение», созданное затмением. Если вы не хотите зависеть от сторонней библиотеки, напишите однострочный метод, например 'Objects.equal'. Даже когда используется только один или два раза, это делает код намного лучше! – maaartinus
@maaartinus 'equals' /' hashCode' одна линия методов ??? – FrVaBe
Если вы просто имеем дело с бина сущности, где идентификатор является первичным ключом, вы можете упростить.
@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, особенно примеры из ответа, сравнивающие длину, имя и дочерние элементы для некоторого объекта. 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() на всех реквизитах, что у нас есть?
- Когда вы помещаете сущность2 в постоянный набор, где сущность1 уже существует, это будет помещено дважды и приведет к исключению во время фиксации.
- Если вы хотите присоединить отсоединенный объект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 (я никогда не использую составные клавиши).
На всякий случай, другие сочтут это полезным, я придумал этот класс 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 является максимально разумное число свойств в модели предметной области, если у вас есть больше, вы должны думать рефакторинга и введения большего количества классов вместо сохранения кучи строк и примитивов.
Недостатки: это не полезно, если у вас есть в основном примитивы и/или массивы, которые вам нужны для хэширования.(Обычно это так, когда вам приходится иметь дело с плоскими (переносными) объектами, которые находятся вне вашего контроля).
Люди, проснись! С Java 7 есть вспомогательные методы для equals и hashCode в стандартной библиотеке. Их использование полностью эквивалентно использованию методов Guava.
a) в то время, когда задавался этот вопрос, Java 7 wasn там еще нет б) технически, они не совсем эквивалентны. jdk имеет метод Objects.equals и методы ObjectAsequal Guava. Я могу использовать статический импорт только с версией Guava. Это просто косметика, я знаю, но это делает негуаву значительно более загроможденным. –
Это не является хорошим методом для переопределения объектов равным методу из-за того, что Objects.equals вызовет метод .equals экземпляра. Если вы вызовете Objects.equals в методе .equals экземпляра, это приведет к переполнению стека. – dardo
Можете ли вы привести пример, когда он попадает в петлю? –
Просто не искушайтесь функциями 'reflectionEquals' и' reflectionHashcode'; производительность является абсолютным убийцей. – skaffman
Я видел некоторое обсуждение здесь о равных вчера и имел некоторое свободное время, поэтому я сделал быстрый тест. У меня было 4 объекта с разными реализациями равных. eclipse generated, equalsbuilder.append, equalsbuilder.reflection и pojomatic аннотации. Базой было затмение. equalsbuilder.append занял 3.7x. pojomatic взял 5 раз. на основе отражения взято 25.8x. Это было довольно обескураживающе, потому что мне нравилась простота отражения, и я не могу вынести имя «pojomatic». – digitaljoel
Другим вариантом является Project Lombok; он использует генерацию байт-кода вместо отражения, поэтому он должен работать так же, как и Eclipse-сгенерированный. http://projectlombok.org/features/EqualsAndHashCode.html – Miles