Coder
17 уровень

Перегрузка методов equals() и hashCode() в Java

Пост из группы Архив info.javarush.ru
3786 участников

Переопределение методов equals() и hashCode() в Java

Equals и hashCode являются фундаментальными методами объявленные в классе Object и содержатся в стандартных библиотеках Java. Метод еquals() используется для сравнения объектов, а hashCode - для генерации целочисленного кода объекта. Эти методы широко используются в стандартных библиотеках Java при вставке и извлечению объектов в HashMap. Метод equal также используется для обеспечения хранения только уникальных объектов в HashSet и других Set реализациях, а также в любых других случаях, когда нужно сравнивать объекты. Реализация по умолчанию метода equals() в классе java.lang.Object сравнивает ссылки на адреса в памяти, которые хранят переменные, и возвращает true только в том случае, если адреса совпадают, другими словами переменные ссылаются на один и тот же объект. Java рекомендует переопределять методы equals() и hashCode(), если предполагается, что сравнение должно осуществляться в соответсвии с естественной логикой или бизнес-логикой. Многие классы в стандартных библиотеках Java переопределяет их, например в классе String переопределяется equals таким образом, что возвращается true, если содержимое двух сравниваемых объектов одинаковое. В классе-обертке Integer метод equal переопределяется для выполнения численного сравнения, и так далее.
Так как HashMap и HashTable в Java полагаются на методы equals() и hashCode() для сравнения своих key и values, то Java предлагает следующие правила для переопределения этих методов:
  1. Рефлексивность: Объект должен равняться себе самому.

  2. Симметричность: если a.equals(b) возвращает true, то b.equals(a) должен тоже вернуть true.

  3. Транзитивность: если a.equals(b) возвращает true и b.equals(c) тоже возвращает true, то c.equals(c) тоже должен возвращать true.

  4. Согласованность: повторный вызов метода equals() должен возвращать одно и тоже значение до тех пор, пока какое-либо значение свойств объекта не будет изменено. То есть, если два объекта равны в Java, то они будут равны пока их свойства остаются неизменными.

  5. Сравнение null: объект должны быть проверен на null. Если объект равен null, то метод должен вернуть false, а не NullPointerException. Например, a.equals(null) должен вернуть false.

Соглашение между equals и hashCode в Java.

  1. Если объекты равны по результатам выполнения метода equals, тогда их hashcode должны быть одинаковыми.

  2. Если объекты не равны по результатам выполнения метода equals, тогда их hashcode могут быть как одинаковыми, так и разными. Однако для повышения производительности, лучше, чтобы разные объекты возвращали разные коды.

Как переопределять метод equals в Java

1.
@Override
public boolean equals(Object obj) {
/*1. Проверьте*/if (obj == this) {
/*и верните */ return true;
         }
2. Проверьте объект на null, а также проверьте, чтобы объекты были одного типа. Не делайте проверку с помощью instanceof так как такая проверка будет возвращать true для подклассов и будет работать правильно только в случае если ваш класс объявлен как immutable. Вместо этого можно использовать getClass();
if (obj == null || obj.getClass() != this.getClass()) {
            return false;
}
3. Объявите переменную типа, который вы сравниваете, и приведите obj к этому типу. Потом сравнивайте каждый атрибут типа начиная с численных атрибутов (если имеются) потому что численные атрибуты проверяются быстрей. Сравнивайте атрибуты с помощью операторов И и ИЛИ (так называемые short-circuit logical operators) для объединения проверок с другими атрибутами.
Person guest = (Person) obj;
        return id == guest.id && (firstName == guest.firstName ||
            (firstName != null && firstName.equals(guest.getFirstName())))
                && (lastName == guest.lastName || (lastName != null &&                      lastName .equals(guest.getLastName())));
}
Полный пример переопределения метода equals в Java
/** * Person class with equals and hashcode implementation in Java * @author Javin Paul */
public class Person {
    private int id;
    private String firstName;
    private String lastName;

    public int getId() { return id; }
    public void setId(int 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; }
    @Override
    public boolean equals(Object obj) {
    if (obj == this) {
        return true;
    }
    if (obj == null || obj.getClass() != this.getClass()) {
        return false;
    }

    Person guest = (Person) obj;
    return id == guest.id
        && (firstName == guest.firstName
            || (firstName != null &&firstName.equals(guest.getFirstName())))        && (lastName == guest.lastName
            || (lastName != null && lastName .equals(guest.getLastName())
            ));
    }
    @Override
    public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());             result = prime * result + id; result = prime * result +
        ((lastName == null) ? 0 : lastName.hashCode()); return result;
    }
 }

Распространенные ошибки при переопределении equals в Java

  1. Вместо того, чтобы переопределять метод equals (Override) программист перегружает его (Overload)Синтаксис метода equals() в классе Object определен как public boolean equals(Object obj), но многие программисты ненароком перегружают метод: public boolean equals(Person obj) - вместо Object в качестве аргумента используют имя своего класса (напр. Person). Эту ошибку сложно обнаружить из-за static binding. Таким образом, если вы вызовите этот метод для объекта своего класса, то метод не просто скомпелируется, а даже сделает это корректно. Однако, если вы положите ваш объект в коллекцию, например ArrayList и вызовите метод contains(), работа которого основана на методе equals(), то метод contains не сможет обнаружить ваш объект.

  2. При переопределении метода equals() не проверять на null переменные, что в конечном итоге заканчивается NullPointerException при вызове equals(). Ниже представлен корректный код. firstname == guest.firstname || (firstname != null && firstname.equals(guest.firstname));
  3. Третья распространенная ошибка это не переопределять метод hashCode(), а только equals(). Вы обязаны переопределять оба метода equals() и hashCode() в Java. Метод hashCode используется в hash -коллекциях(например HashSet), и чем меньше будет коллизий (одинаковый код при разных объектах) тем эффективнее эти коллекции будут работать с объектами вашего класса.

  4. Последняя распространенная ошибка программистов в том, что при переопределении метода equals() не сохраняется соответствие между методами equals() и compareTo(), что является неформальным требованием для избежания хранения дубликатов в Set (SortedSet, TreeSet).

Подсказки как писать в Java метод equals

  1. Большинство IDE такие как NetBeans, Eclipse и IntelliJ IDEA обеспечивают поддержку генерации методов equals() и hashCode(). В Eclipse нажмите правую кнопку -> source -> generate equals() и hashCode().

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

  3. При переопределении hashCode() в Java удостоверьтесь в использовании всех полей, что были использованы в методе equals().

  4. String и классы-оболочки такие как Integer, Float и Double переопределяют метод equals(), но StringBuffer не переопределяет.

  5. При любой возможности делайте поля immutable используя final переменные в Java.

  6. При сравнении String объектов используйте equals() вместо оператора ==.

  7. Два объекта которые логически равны, но загружены из разных ClassLoader не могут быть равными. Помните, что проверка с помощью getClass() вернет false если класс-загрузчик разный.

  8. Используйте @Override аннотацию также для метода hashCode, так как это предупреждает неуловимые ошибки, например возвращаемое значение метода int, однако некоторые программисты возвращают long.

P.S. Уважаемые коллеги! Мне оказалась полезной эта статья при решении задач 21 го уровня! Желаю удачи при разборе данной темы, пользуйтесь переводом! Я надеюсь, что вы мне поможете поднять мой рейтинг, так как сейчас я даже не могу оставлять комментарий на этом форуме. Всем огромное спасибо! Оригинал статьи Я некоторые моменты опустил в связи с нехваткой свободного времени, однако перевел все, что необходимо знать для решения задач 21о уровня.
Комментарии (5)
  • популярные
  • новые
  • старые
Для того, что бы оставить комментарий вы должны авторизоваться
Rihard198525 уровень
31 июля, 15:49
А ты опасный хакер)
ShaggyM13 уровень, Минск
19 июля, 11:41
Или исправьте заголовок, или уберите статью. Перегрузка и переопределение - разные понятия.
Rodriguez27 уровень
15 июля, 00:12
А можно подробнее вот эту часть?
return id == guest.id
        && (firstName == guest.firstName
            || (firstName != null &&firstName.equals(guest.getFirstName())))        && (lastName == guest.lastName
            || (lastName != null && lastName .equals(guest.getLastName())
            ));
Почему мы сравниваем переменные или через equals или через равенство, причем , там где равенство, мы не делаем проверку на null, а где equals - делаем?
Олег27 уровень, Киев
4 июня, 14:04
Добрейший день, простите но разве тут : 3) Транзитивность: если a.equals(b) возвращает true и b.equals(c) тоже возвращает true, то c.equals(c) тоже должен возвращать true. не должно бЬІть так : если a.equals(b) возвращает true и b.equals(c) тоже возвращает true, то (а, а не с) a.equals(c) тоже должен возвращать true. ?
Max Petrov36 уровень
15 июня, 12:28
если не ошибаюсь опечатка