Каков корректный способ переопределить хэш-код () и равняется () методам персистентного объекта?

У меня есть простой класс Роль:

@Entity
@Table (name = "ROLE")
public class Role implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;
    @Column
    private String roleName;

    public Role () { }

    public Role (String roleName) {
        this.roleName = roleName;
    }

    public void setId (Integer id) {
        this.id = id;
    }

    public Integer getId () {
        return id;
    }

    public void setRoleName (String roleName) {
        this.roleName = roleName;
    }

    public String getRoleName () {
        return roleName;
    }
}

Теперь я хочу переопределить его методы, равняется и хэш-код. Мое первое предложение:

public boolean equals (Object obj) {
    if (obj instanceof Role) {
        return ((Role)obj).getRoleName ().equals (roleName);
    }
    return false;
}

public int hashCode () {
    return id; 
}

Но когда я создаю новый Ролевой объект, его идентификатор является пустым. Вот почему у меня есть некоторая проблема с реализацией метода хэш-кода. Теперь я могу просто возвратиться roleName.hashCode () но что, если roleName не является необходимым полем? Я почти уверен, что не настолько трудно составить более сложный пример, который не может быть решен путем возврата хэш-кода одного из его полей.

Таким образом, я хотел бы видеть некоторые ссылки на связанные обсуждения или услышать Ваш опыт о решении этой проблемы. Спасибо!

13
задан Roman 18 December 2009 в 13:27
поделиться

5 ответов

Книга Бауэра и Кинга Сохранение Java с Hibernate не рекомендует использовать ключевое поле для equals и hashCode. Они советуют вам выбрать, какими были бы ключевые поля бизнес-объекта (если бы не было искусственного ключа), и использовать их для проверки равенства. В этом случае, если бы имя роли не было обязательным полем, вы бы нашли нужные поля и использовали бы их в комбинации. В случае с кодом, который вы публикуете, где имя роли - это все, что у вас есть, кроме идентификатора, я бы выбрал имя роли.

Вот цитата со страницы 398:

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

Равенство бизнес-ключей означает, что метод equals () сравнивает только те свойства, которые образуют бизнес-ключ. Это идеальное решение, позволяющее избежать всех проблем, представленных ранее. Единственным недостатком является то, что для определения правильного бизнес-ключа в первую очередь требуется дополнительное внимание. Это усилие в любом случае требуется; важно идентифицировать любые уникальные ключи, если ваша база данных должна обеспечивать целостность данных с помощью проверки ограничений.

Простой способ, который я использую для создания метода equals и hashcode, - это создать метод toString, который возвращает значения полей «бизнес-ключ» , затем используйте это в методах equals () и hashCode (). РАЗЪЯСНЕНИЕ: Это ленивый подход, когда меня не беспокоит производительность (например, во внутренних веб-приложениях с нестандартной производительностью), если производительность, как ожидается, будет проблемой, тогда напишите методы самостоятельно или используйте средства генерации кода вашей IDE.

13
ответ дан 1 December 2019 в 21:24
поделиться

Бизнес-ключ объекта может требовать его родительского (или другого взаимно-однозначного или многозначного) отношения. В этих случаях вызов equals () или hashcode () может привести к попаданию в базу данных. Помимо производительности, закрытие сеанса вызовет ошибку. Я в основном отказался от попыток использовать бизнес-ключи; Я использую основной идентификатор и избегаю использования несохраненных сущностей в картах и ​​наборах. Пока работает хорошо, но это, вероятно, зависит от приложения (будьте осторожны при сохранении нескольких дочерних элементов через родительский каскад). Иногда я использую отдельное бессмысленное ключевое поле, которое автоматически создается в конструкторе или создателем объекта uuid.

2
ответ дан 1 December 2019 в 21:24
поделиться

Знать, когда переопределить хэш-код и равенство - непростая задача, есть еще одно обсуждение, где у вас есть пример и ссылка на документацию здесь Какие проблемы следует учитывать при переопределении equals и hashCode в Java?

1
ответ дан 1 December 2019 в 21:24
поделиться

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

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

1
ответ дан 1 December 2019 в 21:24
поделиться

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

Во-первых, другие упоминали, как работать с возможностью нуля, но один критический элемент хорошей пары методов hashcode() и equals() заключается в том, что они должны подчиняться договору, а ваш код, приведенный выше, этого не делает. Контракт заключается в том, что объекты , для которых equals() возвращает true, должны возвращать равные значения хэшкода, но в вашем классе выше поля id и roleName независимы.

Это фатально ошибочная практика: вы легко можете иметь два объекта с одинаковым значением roleName, но разными значениями id.

Практика заключается в использовании одних и тех же полей для генерации значения хэшкода, которые используются методом equals(), и в одном и том же порядке. Ниже приведена моя замена вашему методу хэшкода:


public int hashCode () {
    return ((roleName==null) ? 0 : roleName.hashcode()); 
}

Примечание: Я не знаю, что вы подразумевали под использованием поля id в качестве хэшкода, или что вы подразумевали под использованием поля id. Из примечания я вижу, что он генерируется, но он генерируется внешне, так что класс в том виде, в котором он написан, не выполняет контракт.

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

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

И как хорошее общее правило, если вам приходится это делать, вы, вероятно, допустили ошибку при проектировании. Не всегда, но вероятно. Одна из причин? Люди не всегда читают комментарии, так что даже если вы создадите идеально функционирующую систему, со временем кто-то "злоупотребит" вашим классом и создаст проблемы.

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

.
8
ответ дан 1 December 2019 в 21:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: