Подтвердить что ты не робот

Стиль использования HashCodeBuilder и EqualsBuilder

Я часто использую apache HashCodeBuilder и EqualsBuilder для равенства объектов, используя отражение, но недавно я, коллега, сказал мне, что использование отражения может привести к огромной производительности, если объект содержит множество свойств. Взволнованный, что я могу использовать неправильную реализацию, мой вопрос в том, какой из следующих способов вы предпочтете? И почему?

public class Admin {

    private Long id;
    private String userName;

    public String getUserName() {
        return userName;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Admin)) {
            return false;
        }
        Admin otherAdmin  = (Admin) o;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(getUserName(), otherAdmin.getUserName());
        return builder.isEquals();
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(getUserName());
        return builder.hashCode();
    }
}

Vs.

public class Admin {

    private Long id;
    private String userName;

    public String getUserName() {
        return userName;
    }

    @Override
    public boolean equals(Object o) {
      return EqualsBuilder.reflectionEquals(this, o, Arrays.asList(id));
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this, Arrays.asList(id));
    }
}
4b9b3361

Ответ 1

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

Я поеду на первый вариант, если бы был в вашей ситуации. Также есть ошибка в вашем первом подходе при генерации hashCode это должен быть return builder.toHashCode(); вместо return builder.hashCode(); (который возвращает hashcode объектов hashcode builder)

Ответ 2

Я бы предпочел второй вариант по двум причинам:

  • Очевидно, что читать легче

  • Я бы не стал использовать аргументы производительности для первого параметра, если только они не включают соответствующую метрику. Например. сколько миллисекунд будет основано на отражениях "равно", добавить к типичной латентности конца в конец? В целом, какой процент будет увеличиваться? Не зная, что хорошие шансы на то, что оптимизация преждевременна

Ответ 3

Несмотря на то, что второй вариант более привлекателен (потому что это всего лишь одна строка кода), я бы выбрал первый вариант.

Причина - просто производительность. После небольшого теста я нашел очень большую разницу во времени между ними.

Чтобы получить представление о времени, я создал два простых класса:

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class1 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class1(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class1() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object o) {
      return EqualsBuilder.reflectionEquals(this, o);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

}

и

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class2 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class2(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class2() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Class2)) {
            return false;
        }
        Class2 other = (Class2) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(field1, other.field1);
        builder.append(field2, other.field2);
        builder.append(field3, other.field3);
        builder.append(field4, other.field4);
        builder.append(field5, other.field5);
        builder.append(field6, other.field6);
        return builder.isEquals();
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(getField1());
        builder.append(isField2());
        builder.append(getField3());
        builder.append(getField4());
        builder.append(getField5());
        builder.append(getField6());
        return builder.hashCode();

    };

}

Этот второй класс почти такой же, как первый, но с разными equals и hashCode.

После этого я создал следующие тесты:

package equalsbuildertest;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.Date;

import org.junit.Test;

public class EqualsBuilderTest {

    @Test
    public void test1() {
        Class1 class1a = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class1 class1b = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class1a, class1b);
        }
    }

    @Test
    public void test2() {
        Class2 class2a = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class2 class2b = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class2a, class2b);
        }
    }

}

Тест довольно прост и служит только для измерения времени.

Результаты были следующими:

  • test1 (2,024 s)
  • test2 (0,039 s)

Я выбрал их полностью равными, чтобы иметь наибольшее время. Если вы решите выполнить тест с условиями NotEquals, у вас будет меньше времени, но также будет очень большой разницы во времени.

Я запускаю эти тесты на 64-битном процессоре Intel Core i5-3317U с частотой 1,70 ГГц x4 с Fedora 21 и Eclipse Luna.

В заключение я бы не стал рисковать такой большой разницей в производительности, чтобы сохранить пару строк кода, которые вы, возможно, не можете вводить в любом случае с помощью шаблона (в Eclipse под Windows → Настройки находятся в Java → Редактор → Шаблоны), например:

${:import(org.apache.commons.lang3.builder.HashCodeBuilder, org.apache.commons.lang3.builder.EqualsBuilder)}
@Override
public int hashCode() {
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
    hashCodeBuilder.append(${field1:field});
    hashCodeBuilder.append(${field2:field});
    hashCodeBuilder.append(${field3:field});
    hashCodeBuilder.append(${field4:field});
    hashCodeBuilder.append(${field5:field});
    return hashCodeBuilder.toHashCode();
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    ${enclosing_type} rhs = (${enclosing_type}) obj;
    EqualsBuilder equalsBuilder = new EqualsBuilder();
    equalsBuilder.append(${field1}, rhs.${field1});
    equalsBuilder.append(${field2}, rhs.${field2});
    equalsBuilder.append(${field3}, rhs.${field3});
    equalsBuilder.append(${field4}, rhs.${field4});
    equalsBuilder.append(${field5}, rhs.${field5});${cursor}
    return equalsBuilder.isEquals();
}

Ответ 4

Ваш написанный вами вопрос ясно иллюстрирует одно из преимуществ второго подхода:

В первом случае очень легко сделать ошибку return builder.hashCode() вместо правильного return builder.toHashCode(), что приведет к тонким ошибкам, которые могут быть очень трудными для отслеживания.

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

Ответ 5

Я бы сказал, что ни одна из них не является хорошей реализацией. Я бы сказал, что EqualsBuilder не является хорошей основой для использования по следующим причинам:

  • Не расширяется. Что, если одно из полей, которые вы пытаетесь утвердить равенство, должно обрабатывать значения null и blank равными?
  • Вы должны сохранить список переменных, как если бы они были жестко заданными переменными. Вы должны указать все переменные, которые вы хотите сравнить. На данный момент нет никакого различия между параметрами == o.getA() && b == o.getB()...
  • Использование отражения требует дополнительного ресурса, как вы указали, и в корпоративном приложении, которое разбивает миллиарды объектов. Выполнение этого отражения равно так же плохо, как утечка памяти.

Я скажу, что должна быть лучшая структура, чем Apache.

Ответ 6

Согласитесь с @Churk, Apache HashCodeBuilder и EqualsBuilder не реализованы хорошо. HashCodeBuilder все еще играет с простыми номерами! Кроме того, он делает очень много ненужной работы. Вы читали источник?

Так как Java 5 (если не раньше), AbstractHashMap < > не использовал по модулю простого числа, чтобы найти хэш-ведро. Вместо этого количество ведер - это мощность двух, а младшие бит N-х хэш-кода используются для определения местоположения ведра.

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

Таким образом, правильный способ переопределения int Object.hashCode() заключается в возврате простейшего, постоянного значения с наивысшей arity в совокупности объектов, которые будут сожительствовать в любой коллекции с использованием класса.

Как правило, значение немодифицированного идентификатора - ваш лучший выбор. Если ваше поле идентификатора является целостным, просто отбросьте его (int) и верните. Если это String или другой объект, просто верните его хэш-код. Вы поняли эту идею. Для составного идентификатора возвращайте поле (или его hashCode) с самыми разными значениями. Меньше - больше.

Конечно, контракт между hashCode() и equals() должен быть выполнен. Таким образом, equals() должно быть реализовано соответствующим образом. hashCode() не обязательно использовать полные квалификаторы, необходимые для равенства, но любые поля, используемые в hashCode(), должны использоваться в equals(). Здесь методы, подобные StringUtils.equals(s1, s2), полезны для непрерывного и безопасного обращения с нулевыми значениями.