Apache Commons дорівнює / конструктору hashCode [закрито]


155

Мені цікаво знати, що тут думають люди про використання org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder для впровадження equals/ hashCode? Це було б кращою практикою, ніж писати власну? Це добре грає зі сплячим режимом? Яка твоя думка?


16
Просто не спокушайтесь функціями reflectionEqualsта reflectionHashcode; вистава - абсолютний вбивця.
скафман

14
Я вчора побачив тут деякі обговорення рівних і мав вільний час, тому зробив швидкий тест. У мене було 4 об'єкти з різними реалізаціями. породжене затемнення, еквівалент будівельника, додаток, рівномірне відображення та пожоматичні анотації. Базовою лінією було затемнення. equalsbuilder.append взяв 3,7x. пожоматик взяв 5х. Роздум на основі займав 25,8x. Це було дуже відлякує, тому що мені подобається простота роздумів, і я не витримую назви "пожомати".
digitaljoel

5
Інший варіант - Project Lombok; він використовує генерацію байт-кодів, а не відображення, тому він повинен виконуватись так само, як і генерований Eclipse. projectlombok.org/features/EqualsAndHashCode.html
Майлз

Відповіді:


212

Commons / lang builders - це чудово, і я використовую їх протягом багатьох років, не відчуваючи накладних витрат (із сплячим режимом та без нього). Але, як пише Ален, спосіб Гуави ще приємніший:

Ось зразок Біна:

public class Bean{

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

}

Ось рівні () та 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()виклик повертає помилкове значення (справедливо: commons / lang має ObjectUtils.equals(obj1, obj2)метод з однаковою семантикою, який можна використовувати замість того, EqualsBuilderщоб дозволити коротке замикання, як зазначено вище).

Отже: так, спільнокомандуючим спільнокореневим будівництвом дуже зручніше, ніж створені вручну equals()та hashCode()методи (або ті жахливі монстри Eclipse генерують для вас), але версії Java 7+ / Guava ще кращі.

І примітка про сплячку:

будьте обережні щодо використання ледачих колекцій у ваших реалізаціях equals (), hashCode () та toString (). Це вийде з ладу, якщо у вас немає відкритої сесії.


Примітка (про рівних ()):

а) в обох версіях рівняння () вище, ви можете також використовувати один або обидва ці ярлики:

@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насправді може бути кращим рішенням.


34
Те ж саме буде можливим і з Java 7 Objects.equals: download.oracle.com/javase/7/docs/api/java/util/…
Thomas Jung

3
Якщо я читаю це правильно, Джош Блок каже в " Ефективній Java" , пункт 8, що ви не повинні використовувати метод getClass () у вашому методі equals (); скоріше слід використовувати instanceof.
Джефф Олсон

6
@SeanPatrickFloyd Шлях Guava не тільки створює об’єкт масиву для varargs, він також перетворює ВСІ параметри на об’єкти. Отже, коли ви передасте йому значення 10 int, ви отримаєте 10 об'єктів Integer та об'єкт масиву. Рішення commons-lang створює лише один об'єкт, незалежно від того, скільки значень ви додаєте до хеш-коду. Та сама проблема з equals. Guava перетворює всі значення в об'єкти, commons-lang створює лише один новий об'єкт.
kayahr

1
@wonhee Я абсолютно не згоден, що це краще. Використання Reflection для обчислення хеш-кодів - це не те, що я б коли-небудь робив. Виробничі накладні витрати, мабуть, незначні, але це просто не так.
Шон Патрік Флойд

1
@kaushik виготовлення фіналу класу фактично вирішує потенційні проблеми обох версій (instanceof і getClass ()), якщо ви реалізуєте свої рівняння () лише для листкових класів
Шон Патрік Флойд

18

Люди, прокиньтесь! Оскільки у Java 7 існують допоміжні методи для рівних та хеш-кодів у стандартній бібліотеці. Їх використання повністю еквівалентно використанню методів Гуави.


а) на той час, коли це питання було задано, Java 7 ще не був b) технічно вони не зовсім еквівалентні. jdk має метод Object.equals порівняно з методами Guava Objects.equal. Я можу використовувати статичний імпорт лише у версії Guava. Я знаю, що це просто косметика, але це робить не-гуаву помітно більш захаращеною.
Шон Патрік Флойд

Це не гарний метод переосмислення методу рівних об'єктів через те, що Objects.equals буде викликати метод .equals екземпляра. Якщо ви викликаєте Objects.equals в методі .equals методу, це призведе до переповнення стека.
Дардо

Чи можете ви навести приклад, коли він потрапляє в петлю?
Михайло Голубцов

OP просить змінити метод equals () в межах Об'єкта. Відповідно до документації статичного методу Objects.equals (): "Повертає істину, якщо аргументи рівні один одному і помилкові в іншому випадку. Отже, якщо обидва аргументи є недійсними, повертається true і якщо точно один аргумент є нульовим, false є повернуто. В іншому випадку рівність визначається за допомогою методу equals першого аргументу. "Тому, якщо ви використовували Objects.equals () в перемененому екземплярі дорівнює (), він називатиме його власним методом equals, тоді Objects.equals () потім знову, надаючи переповнення стека.
Дардо

@dardo Ми говоримо про реалізацію структурної рівності, тому це означає, що два об'єкти рівні між собою, якщо їх поля . Дивіться приклад Guava вище, як реалізується рівність.
Михайло Голубцов

8

Якщо ви не хочете залежати від сторонньої бібліотеки (можливо, ви працюєте на пристрої з обмеженими ресурсами) і навіть не хочете вводити свої власні методи, ви також можете дозволити IDE виконати цю роботу, наприклад, при використанні затемнення

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

Ви отримаєте «рідний» код , який ви можете налаштувати , як вам подобається , і який ви повинні підтримувати на зміни.


Приклад (затемнення Юнони):

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

}

14
Щоправда, але код, породжений Eclipse, не читається та неможливо досягти.
Шон Патрік Флойд

6
Будь ласка, ніколи не думайте про щось таке жахливе, як породжене затемнення equals. Якщо ви не хочете залежати від сторонньої бібліотеки, тоді напишіть однорядковий метод, як Objects.equalви самі. Навіть коли використовується лише один чи два рази, це робить шлях кодом кращим!
maaartinus

@maaartinus equals/ hashCodeметодами одного рядка ???
FrVaBe

1
@maaartinus Guava - третя партійна бібліотека. Я зазначив, що моє рішення може бути використане, якщо ви хочете ВИМОЖИТИ, використовуючи сторонні бібліотеки.
FrVaBe

1
@FrVaBe: І я писав: "Якщо ви не хочете залежати від сторонньої бібліотеки, тоді напишіть однорядний метод, як" Objects.equal "." І тоді я написав однорядковий метод, який ви можете використовувати для AVOID, використовуючи Guava, і все ж скоротити довжину дорівнює приблизно половині.
maaartinus

6

У EqualsBuilder і HashCodeBuilder є два основних аспекти, які відрізняються від написаного вручну коду:

  • нульова обробка
  • створення примірника

EqualsBuilder і HashCodeBuilder полегшують порівняння полів, які можуть бути нульовими. З написаним вручну кодом це створює багато котлован.

З іншого боку, EqualsBuilder створить екземпляр на виклик методу equals. Якщо ваш метод рівних дзвінків часто, це створить безліч примірників.

Для Hibernate рівні та реалізація hashCode не мають значення. Вони - лише деталь реалізації. Практично для всіх об’єктів домену, завантажених в сплячку, накладні витрати (навіть без аналізу результатів) Builder можна ігнорувати . База даних та накладні комунікації будуть значними.

Як згадував skaffman, версію відображення не можна використовувати у виробничому коді. Рефлексія буде повільною, а "реалізація" не буде правильною для всіх, крім найпростіших класів. Врахування всіх членів також небезпечно, оскільки нововведені члени змінюють поведінку методу рівних. Версія для відображення може бути корисною для тестового коду.


Я не погоджуюся з тим, що реалізація рефлексії "не буде правильною для всіх, крім найпростіших класів". За допомогою будівельників ви можете явно виключити поля, якщо вам подобається, тому реалізація дійсно залежить від визначення вашого бізнес-ключа. На жаль, я не можу погодитися з аспектом продуктивності реалізації, що базується на роздумах.
digitaljoel

1
@digitaljoel Так, ви можете виключити поля, але ці визначення не рефакторинг зберігають. Тому я їх спеціально не згадував.
Томас Юнг


0

Якщо ви просто маєте справу з об'єктом суті, де 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();
   }

0

На мою думку, це не добре грає зі сплячим режимом, особливо приклади з відповіді, де порівнюються довжина, ім’я та діти для якоїсь сутності. Спящий радить використовувати діловий ключ для використання в рівнях () і хеш-коді (), і вони мають свої причини. Якщо ви використовуєте генератор auto equals () та hashCode () у вашому бізнес-ключі, це нормально, просто проблеми з продуктивністю потрібно враховувати, як згадувалося раніше. Але люди зазвичай використовують усі властивості, що IMO дуже помиляється. Наприклад, я зараз працюю над проектом, де об'єкти написані за допомогою Pojomatic з @AutoProperty, що я вважаю дійсно поганою схемою.

Їх два основних сценарії використання hashCode () та 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 () хеш-код () на всіх реквізитах, що ми маємо?

  1. Коли ви помістите сутність2 до стійкого набору, де сутність1 вже існує, це буде поставлено двічі і призведе до виключення під час фіксації.
  2. Якщо ви хочете приєднати відокремлену сутність2 до сеансу, де entit1 вже існує, вони (мабуть, я цього особливо не перевіряв) не будуть об'єднані належним чином.

Отже, для 99% проекту, який я роблю, ми використовуємо наступну реалізацію рівнянь () та 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();
}

Для перехідної сутності я роблю те саме, що зробить сплячий на кроці наполегливості, тобто. Я використовую відповідність екземпляра. Для стійких об'єктів я порівнюю унікальний ключ, який є таблицею / 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 - це максимально розумна кількість властивостей у доменній моделі, якщо у вас є більше, вам слід подумати про рефакторинг та введення більшого класу, а не підтримувати купу струн та примітивів.

Недоліками є: це не корисно, якщо у вас є в основному примітиви та / або масиви, які вам потрібно глибоко хешувати. (Зазвичай це випадок, коли вам доводиться мати справу з плоскими (передавальними) об'єктами, які знаходяться поза вашим контролем).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.