JavaRush /Java блог /Архив info.javarush /Работа с методами hashCode() и equals() в языке Java
Lenchik854
0 уровень
Chernihiv

Работа с методами hashCode() и equals() в языке Java

Статья из группы Архив info.javarush
В этом посте я изложу свое понимание методов hashCode() и equals(). Я хочу рассказать об их реализации по умолчанию, а также о том как корректно переопределить их. Я также напишу о реализации этих методов, используя Apache Common package’s вспомогательные классы. Работа с методами hashCode() и equals() в языке Java - 1Содержание этого поста:
  1. Использование hashCode() и equals().
  2. Переопределение поведения по умолчанию.
  3. Переопределение hashCode() и equals(), используя Apache Commons Lang.
  4. То, что важно помнить.
  5. Особое Внимание При Использовании ORM.
Методы hashCode() и equals() были определены в классе Object, который является родительским классом для объектов java. Поэтому все java объекты наследуют от этих методов реализацию по умолчанию.

Использование hashCode() и equals()

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

Переопределение поведения по умолчанию

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

public class Employee
{
    private Integer id;
    private String firstname;
    private String lastName;
    private String department;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getFirstname() {
        return firstname;
    }
    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getDepartment() {
        return department;
    }
    public void setDepartment(String department) {
        this.department = department;
    }
}
Описанный выше класс Employee имеет некоторые основные атрибуты и методы доступа. Сейчас рассмотрим простую ситуацию, где необходимо сравнить два объекта класса Employee.

public class EqualsTest {
    public static void main(String[] args) {
        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);
        //Печатает false в консоли
        System.out.println(e1.equals(e2));
    }
}
Не нужно быть ясновидящим, чтобы догадаться, что написанный выше метод вернет “false”. Но правильно ли это на самом деле, учитывая, что эти оба объекта одинаковые? В real time application метод должен вернуть true. Чтобы достигнуть корректного поведения, нам нужно переопределить метод equals(), как и сделано ниже:

public boolean equals(Object o) {
        if(o == null)
        {
            return false;
        }
        if (o == this)
        {
           return true;
        }
        if (getClass() != o.getClass())
        {
            return false;
        }
        Employee e = (Employee) o;
        return (this.getId() == e.getId());
}
Добавьте этот метод в свой класс Employee, и проверка на эквивалентность вернет “true”. Однако, всё ли мы сделали? Пока нет. Давайте проверим наш модифицированный класс еще одним способом.

import java.util.HashSet;
import java.util.Set;

public class EqualsTest
{
    public static void main(String[] args)
    {
        Employee e1 = new Employee();
        Employee e2 = new Employee();

        e1.setId(100);
        e2.setId(100);

        //Печатает 'true'
        System.out.println(e1.equals(e2));

        Set employees = new HashSet();
        employees.add(e1);
        employees.add(e2);
        //Печатает два объекта
        System.out.println(employees);
    }
}
Команда System.out.println(employee) распечатывает два объекта. Если оба объекта были эквивалентны, а в Set содержатся только уникальные объекты, то внутри HashSet должен быть только один экземпляр, т.е. оба объекта ссылаются на одинаковые экземпляры класса Employee. Что же мы упустили? Мы упустили второй важный метод hashCode(). Как сказано в документации java, если вы переопределяете метод equals(), то вы обязаны переопределить метод hashCode(). Итак, давайте добавим ещё один метод в наш класс Employee.

@Override
 public int hashCode()
 {
    final int PRIME = 31;
    int result = 1;
    result = PRIME * result + getId();
    return result;
 }
Мы добавили один раз этот метод в наш класс, и на печать будет выведен только один объект, и, таким образом, проверка эквивалентности е1 и е2 показала true.

Переопределение hashCode() и equals(), используя Apache Commons Lang

Apache Commons предоставляет два замечательных вспомогательных класса для вызова методов hashCode() и equals(). Ниже смотрим использование:

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Employee
{
 private Integer id;
 private String firstname;
 private String lastName;
 private String department;
public Integer getId() {
    return id;
 }
 public void setId(Integer id) {
    this.id = id;
 }
 public String getFirstname() {
    return firstname;
 }
 public void setFirstname(String firstname) {
    this.firstname = firstname;
 }
 public String getLastName() {
    return lastName;
 }
 public void setLastName(String lastName) {
    this.lastName = lastName;
 }
 public String getDepartment() {
    return department;
 }
 public void setDepartment(String department) {
    this.department = department;
 }
@Override
 public int hashCode()
 {
    final int PRIME = 31;
    return new HashCodeBuilder(getId()%2==0?getId()+1:getId(), PRIME).
           toHashCode();
 }
@Override
 public boolean equals(Object o) {
    if (o == null)
       return false;
    if (o == this)
       return true;
    if (o.getClass() != getClass())
       return false;
    Employee e = (Employee) o;
       return new EqualsBuilder().
              append(getId(), e.getId()).
              isEquals();
    }
 }
С другой стороны, если вы используете один из редакторов кода, они также должны быть способны вызывать некоторые хорошие структуры для вас. Например, если в Eclipse IDE кликнуть правой клавишей на class >> sourse > Generating hashCode() and equals() … будет сгенерирована очень хорошая реализация для вас. Работа с методами hashCode() и equals() в языке Java - 2То, что важно помнить.
  1. Всегда используйте те же атрибуты объекта для вызова и hashCode() и equals(). Как раз в нашем случае, мы использовали employee id.
  2. Метод equals() должен быть устойчивым (если объект не изменялся, метод должен возвращать то же самое значение).
  3. Всякий раз, когда a.equals(b), то a.hashCode() должно быть таким же, как b.hashCode().
  4. Если вы переопределили один метод, то обязательно должны переопределить второй.

Особое Внимание При Использовании ORM

Если вы имеете дело с ORM (ru.wikipedia.org/wiki/ORM), то всегда используйте геттеры и никогда не используйте в hashCode() и equals() ссылки на поля. Это потому, что в ORM, время от времени поля загружаются при помощи отложенной загрузки (lazy load) и не доступны, пока не вызваны их геттеры. Например, в нашем классе Employee, мы используем e1.id == e2.id. Вполне возможно, что поля id загружены с помощью отложенной загрузки. Одно из полей может быть равно 0 или null и мы получим некорректное поведение. Но, если используется e1.getId() == e2.getId(), мы можем быть уверены, даже если поля были загружены с помощью отложенной загрузки; вызов геттера первым заполнит поле. Это всё, что я знаю о методах hashCode() и equals(). Надеюсь, что это где-нибудь кому-нибудь поможет. Удачи в учебе!! p.s. Это моя первая попытка перевода. Постаралась передать всё как можно ближе к тому, что хотел сказать автор. Если есть замечания, пожалуйста, напишите в комментариях. Строго не судите :-))) Oригинал статьи
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Светлана Уровень 17
21 апреля 2024
"По умолчанию, метод hashCode() для объекта возвращает номер ячейки памяти, где объект сохраняется" - неправильно. Это зависит от версии JVM. Уже лет триста, как hashCode() возвращает рандомное число.
Ingenieur Уровень 22
4 апреля 2023
Я вообще не понял, откуда и зачем взялась

final int PRIME = 31;
и зачем производить такие манипуляции с id:

result = PRIME * result + getId();
. Можно было более развернуто обьяснить
Алёна Уровень 8
14 апреля 2020
А что за вычисления выполняет метод hashCode () внутри себя?
виктор Уровень 29
15 сентября 2019
если вы используете один из редакторов кода, они также должны быть способны вызывать некоторые хорошие структуры для вас лучше перевести: они могут предложить вам несколько хороших реализаций
Алексей Уровень 22
15 марта 2019
Эта статья из разряда: "Кто знал это раньше - ничего нового для себя не усвоил", "Кто не знал это раньше - так ничего и не понял". В статье очень много нюансов которые требуют более детального рассмотрения (что это такое? зачем оно здесь? почему именно это? и т.д.)
javam Уровень 25
29 июля 2016
А мне не понятно вот что. У меня equals по String'ам. Как мне сделать hashCode?
DefNeo Уровень 36
27 июля 2016
А мне непонятно: почему везде появляется число 31?
4e4el Уровень 34
1 октября 2014
Мне не понятно зачем использовать Apache Commons Lang, в чем ее преимущество?
З.Ы. А так статья мне понравилась)