Объекты equals (), hashCode () и toString (). Как правильно их реализовать? - PullRequest
32 голосов
/ 15 марта 2010

Я реализую equals(), hashCode() и toString() моих сущностей, используя все доступные поля в бине.

Я получаю Lazy init Exception на внешнем интерфейсе, когда пытаюсь сравнить равенство или когда я печатаю состояние obj. Это потому, что некоторый список в сущности может быть ленивым инициализирован.

Мне интересно, как правильно реализовать equals() и toString() на объекте сущности.

Ответы [ 9 ]

17 голосов
/ 15 марта 2010

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

в toString() вы можете поместить любую интересную информацию - например, все поля.

Используйте вашу IDE (Eclipse, NetBeans, IntelliJ), чтобы сгенерировать все это для вас.

Чтобы избежать LazyInitializationException, независимо от того, в equals() или на ваш взгляд (jsp), вы можете использовать OpenSessionInView.

10 голосов
/ 15 марта 2010

При реализации методов equals и hashCode для объектов Hibernate важно

  1. Используйте геттеры вместо прямого доступа к свойствам класса.
  2. Не сравнивать классы объектов напрямую, а использовать instanceof вместо

Дополнительная информация:

Переполнение стека: переопределение-равно-и-хэш-код в Java

Документация Hibernate: Equals и HashCode

Редактировать: те же правила, что и при отсутствии доступа к свойствам класса, напрямую применяются и к методу toString - только использование методов получения гарантирует, что информация, действительно содержащаяся в классе, будет возвращена.

7 голосов
/ 15 марта 2010
  1. Если два объекта равны, они должны иметь одинаковый хеш-код.
  2. метод equals () по умолчанию проверяет, ссылаются ли две ссылки на один и тот же экземпляр в памяти в куче Java

Вы можете полагаться на идентификатор сущности для сравнения вашей сущности, используя равно

public boolean equals(Object o) {
    if(o == null)
        return false;

   Account account = (Account) o;
   if(!(getId().equals(account.getId())))
       return false;

   return true;
}

Но что происходит, когда у вас есть непостоянная сущность. Это не будет работать , потому что его Идентификатор не был назначен.

Итак, давайте посмотрим, что говорит об этом Java Persistence с Hibernate Book

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

Итак

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

Итак, давайте предположим, что у вас есть пользовательский объект, и его естественные ключи - firstName и lastName (по крайней мере, его / ее firstName и lastName часто не изменяются). Так что это будет реализовано как

public boolean equals(Object o) {
    if(o == null)
        return false;

    if(!(o instanceof User))
        return false;

    // Our natural key has not been filled
    // So we must return false;
    if(getFirstName() == null && getLastName() == null)
        return false;

    User user = (User) o;
    if(!(getFirstName().equals(user.getFirstName())))
        return false;

    if(!(getLastName().equals(user.getLastName())))
        return false;

   return true;
}

// default implementation provided by NetBeans
public int hashcode() {
    int hash = 3;

    hash = 47 * hash + ((getFirstName() != null) ? getFirstName().hashcode() : 0)
    hash = 47 * hash + ((getLastName() != null) ? getLastName().hashcode() : 0)

    retrun hash;
}

Работает отлично! Я использую даже с объектами Mock, такими как репозитории, сервисы и т. Д.

А по поводу метода toString (), как сказал @Bozho, вы можете поместить любую интересную информацию. Но помните, что некоторые веб-фреймворки, такие как, например, Wicket и Vaadin, используют этот метод для отображения его значений.

0 голосов
/ 13 октября 2016
  1. Если у вас есть бизнес-ключ , то вы должны использовать его для equals / hashCode.
  2. Если у вас нет бизнес-ключа, вы не должны оставлять его со значениями по умолчанию Object equals и hashCode, потому что он не работает после вас merge и сущности.
  3. Вы можете использовать идентификатор объекта, как предлагается в этом посте . Единственный улов в том, что вам нужно использовать реализацию hashCode, которая всегда возвращает одно и то же значение, например:

    @Entity
    public class Book implements Identifiable<Long> {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String title;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Book)) return false;
            Book book = (Book) o;
            return getId() != null && 
               Objects.equals(getId(), book.getId());
        }
    
        @Override
        public int hashCode() {
            return 31;
        }
    
        //Getters and setters omitted for brevity
    }
    
0 голосов
/ 02 марта 2016

Если вам пришлось переопределить equals () для объектов Hibernate, убедитесь, что вы выполняете его контракты: -

  • СИММЕТРИЯ
  • REFLECTIVE
  • ПЕРЕХОДНЫЕ
  • ПОСТОЯННО
  • NON NULL

И переопределить hashCode, поскольку его контракт зависит от реализации equals.

Джошуа Блох (дизайнер фреймворка Collection) строго соблюдает это правило

  • пункт 9: Всегда переопределять hashCode, когда вы переопределяете равным

Существуют серьезные непреднамеренные последствия, когда вы не следуете его контракту. Например, List.contains(Object o) может вернуть неправильное значение boolean, поскольку общий контракт не выполнен.

0 голосов
/ 06 января 2015

Это, вероятно, лучший и самый простой способ сделать это:

public String toString() {
    return "userId: " + this.userId + ", firstName: " + this.firstName + ", lastName: " + this.lastName + ", dir: " + this.dir + ", unit: " + this.unit + ", contractExpiryDate: " + this.contractExpiryDate + ", email: " + this.email + ", employeeNumber: " + this.employeeNumber + ", employeeType: " + this.employeeType + ", phone: " + this.phone + ", officeName: " + this.officeName + ", title: " + this.title + ", userType: " + this.userType;
}

public boolean equals(Object obj) {
[...]
return (toString().equals(other.toString()));
}

public int hashCode() {
return toString().hashCode();
}
0 голосов
/ 28 мая 2010

Мы реализуем equals () и hashCode () в нашем суперклассе. Это работает безупречно, особенно в Картах и ​​Списках и т. Д. Это было правильно, так как мы делаем много переходного упорства.

равна ():

/**
 * Compare two entity objects, following hibernate semantics for equality. Here we assume that
 * new objects are always different unless they are the same object. If an object is loaded from
 * the database it has a valid id and therefore we can check against object ids.
 *
 * @see com.dolby.persist.bean.EntityObject#equals(java.lang.Object)
 */
@SuppressWarnings("unchecked")
@Override
public final boolean equals(final Object object) {
    if (this == object) return true;
    if (object == null || this.getClass() != object.getClass()) return false;
    final AbstractModelObject<ID> other = (AbstractModelObject<ID>) object;
    if (this.getId() == null || other.getId() == null) return false;
    return this.getId().equals(other.getId());
}

хэш-код ():

/**
 * Returns an enttiy objects hashcode.
 * <p>
 * What we are doing here is ensuring that once a hashcode value is used, it never changes for
 * this object. This allows us to use object identity for new objects and not run into the
 * problems.
 * </p>
 * <p>
 * In fact the only case where this is a problem is when we save a new object, keep it around
 * after we close the session, load a new instance of the object in a new session and then
 * compare them.
 * </p>
 * <p>
 * in this case we get A==B but a.hashcode != b.hashcode
 * </p>
 * <p>
 * This will work in all other scenarios and don't lead to broken implementations when the
 * propety of the object are edited. The whole point in generating synthetic primary keys in the
 * first place is to avoid having a primary key which is dependant on an object property and
 * which therefore may change during the life time of the object.
 * </p>
 *
 * @see java.lang.Object#hashCode()
 */
@Override
public final synchronized int hashCode() {
    if (this.hashcodeValue == null) {
        if (this.getId() == null) {
            this.hashcodeValue = new Integer(super.hashCode());
        }
        else {
            final int generateHashCode = this.generateHashCode(this.getId());
            this.hashcodeValue = new Integer(generateHashCode);
        }
    }
    return this.hashcodeValue.intValue();
}
0 голосов
/ 15 марта 2010

Моя реализация toString () для объектов Hibernate выглядит следующим образом:

@Override
public String toString() {
    return String.format("%s(id=%d)", this.getClass().getSimpleName(), this.getId());
}

Каждый подкласс моего AbstractEntity (выше) переопределяет этот метод при необходимости:

@Override
public String toString() {
    return String.format("%s(id=%d, name='%s', status=%s)",
            this.getClass().getSimpleName(),
            this.getId(),
            this.getName(),
            this.getStatus());
}

Для hashCode() и equals () помните, что Hibernate часто использует прокси-классы.Таким образом, мой метод equals () обычно выглядит следующим образом:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;

    Class<AbstractEntity> c1 = Hibernate.getClass(this);
    Class<AbstractEntity> c2 = Hibernate.getClass(obj);
    if (!c1.equals(c2)) return false;

    final AbstractEntity other = (AbstractEntity) obj;
    if (this.getId() == null) {
        if (other.getId() != null) return false;
    }
    else if (!this.getId().equals(other.getId())) return false;

    return true;
}

И, как уже говорили другие ... будьте осторожны с доступом к загруженным ленивым свойствам!Простой toString () или даже log.debug (entity) может вызвать огромную активность, если каскадировать несколько лениво загруженных объектов и свойств.

0 голосов
/ 15 марта 2010

Помимо того, что говорили другие, я также думаю, что объект Hibernate все еще должен быть присоединен к сеансу для получения ленивой информации. Без подключения к базе данных эти списки не могут быть загружены:)

...