equals() и hashCode() в Java

Вопрос задан

Изменён 4 года 4 месяца назад

Просмотрен 324 раза

Если мы переопределяем equals(), то обязаны переопределить hasCode(). Причем в обоих методах желательно использовать одни и те же поля класса. Причем, если equals() дает true, то и hashCode() должен быть одинаковым.

Возникает вопрос: а почему бы при переопределении equals() тогда просто не сравнивать hashCode()?

1

Если для двух объектов hashCode возвращает одинаковое значение — это не означает, что это тот же самый объект.

Но если equals

для двух объектов возвращает true, то их hashCode будет одинаковый.

Алгоритм расчёта хэшкода в классе Object не учитывает содержимое класса и реализован так, что при каждом запуске программы, даже у экземпляра класса с одним и тем же содержимым хэш будет разным.

Поэтому, если Вы будете сравнивать экземпляры класса по хэшкоду без переопределения метода hashCode() у Вас всегда будет false.

Собственно, из-за этого и рекомендуется переопределять метод hashCode().

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

Как программист будет определять эквивалентность двух объектов с учётом их содержимого в equals() по большому счёту его личное дело. Только при вычислении хэшкода возможны, т.н. коллизии. Когда два разных объекта имеют одинаковый хэш. По этой причине одинаковые хэшкоды двух и более объектов, ещё не гарантия их эквивалентности. 128 возможных вариантов. Причем полученное, при переборе, входное значение не обязательно будет совпадать с фактическим паролем, но иметь точно такой же хеш.

Метод equals в java объектах как раз и нужен чтоб исключить влияние коллизий на нахождение элементов в коллекциях использующих hashCode и equals, это такие коллекции как HashMap и HashSet. Они используют метод hashCode для определения «места» где должен находится объект, в случае коллизии объекты имеющие идентичный хеш помещаются в список. Поиск среди таких объектов будет осуществляться перебором с вызовом метода equals для каждого элемента списка.

Зарегистрируйтесь или войдите

Регистрация через Google

Регистрация через Facebook

Регистрация через почту

Отправить без регистрации

Почта

Необходима, но никому не показывается

Отправить без регистрации

Почта

Необходима, но никому не показывается

By clicking “Отправить ответ”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Переопределение в Kotlin. Курс «ООП в Kotlin»

Наследование в Kotlin начинается не с объявления программистом какого-то класса родительским. Уже самый первый родительский класс является наследником общего для всех суперкласса Any. Это наследование происходит по-умолчанию, поэтому его можно не указывать. В случае явного указания заголовок класса выглядит примерно так:

class NumInc(n: Int, gap: Int): Any() {
...

Поскольку дочерние классы наследуют свойства и функции родительского, то все, что есть у Any, есть у всех остальных классов. Не важно, непосредственно это класс-наследник или наследник наследника.

Класс Any включает три функции-члена – equals()

, toString(), hashСode(). Если мы создадим пустой класс, у его объектов все-равно будут методы, унаследованные от Any:

fun main() {
    val a = Days()
    val b = Days()
 
    println(a. equals(b)) // или a == b
    println(a.hashCode())
    println(b.hashCode())
    println(a.toString())
    println(a)
}
 
class Days {}

Пример выполнения программы:

false
1018547642
1456208737
Days@3cb5cdba
Days@3cb5cdba

Метод toString() в данном случае можно не вызывать, так как функция println() делает это сама. Этот метод отвечает за преобразование объекта, каких-то данных о нем, к строке. Он должен возвращать строку. В классе

Any запрограммирован возврат строки, содержащей имя класса и через «собаку» по всей видимости адрес объекта в памяти. Нас может не устраивать такое положение дел, лучше бы, чтобы toString() выводит что-нибудь более полезное.

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

Если в классе начать писать имя метода родительского класса, то IntelliJ IDEA сама предложит его для переопределения.

После подтверждения код класса будет выглядеть так:

open class NumInc(n: Int, gap: Int) {
    var number = n
    var step = gap
 
    fun inc() {number += step}
    fun dec() {number -= step}
 
    override fun toString(): String {
        return super.toString()
    }
}

Слово override говорит, что данная функция переопределяет родительскую. String после двоеточия – тип возвращаемых функцией данных, должна возвращаться строка.

В теле автоматически сгенерированной функции происходит обращение к одноименной функции-члену родительского класса. Происходит это через ключевое слово super, которое обозначает родительский класс. Из него мы получаем строку, строка возвращается в функцию toString() класса NumInc, а уже отсюда с помощью return она передается в место, откуда вызывался данный метод.

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

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

open class NumInc(n: Int, gap: Int) {
    var number = n
    var step = gap
 
    fun inc() {number += step}
    fun dec() {number -= step}
 
    override fun toString(): String {
        val n = "number = $number"
        val s = "step = $step"
        return "$n \n $s"
    }
}

Если main будет таким,

fun main() {
    val a = NumInc(0, 1)
    val b = NumInc(12, 2)
    println(a.toString())
    println(b)
}

результат выполнения таким.

number = 0 
 step = 1
number = 12 
 step = 2

Мы можем переопределять не только методы класса Any, но и своего родительского в его дочернем. Пусть нам нужен класс подобный NumInc, но функции inc() и dec() которого уменьшают и увеличивают number на двойной шаг. В этом случае, если этот класс будет дочерним от NumInc, нам придется переопределить функции inc() и dec():

class NumDouble(n: Int, gap: Int): NumInc(n, gap) {
 
    override fun inc() {
        number += step * 2
    }
 
    override fun dec() {
        number -= step * 2
    }
}

Однако мы не сможем этого сделать до тех пор, пока в родительском классе не разрешим переопределять методы с этими именами. Для этого следует объявить как открытые с помощью слова open:

open class NumInc(n: Int, gap: Int) {
    var number = n
    var step = gap
 
    open fun inc() {number += step}
    open fun dec() {number -= step}
    ...
}

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

По-умолчанию методы вместо модификатора open имеют модификатор final. Мы его не пишем.

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

В примере на скрине мы объявили класс NumDouble открытым и создали от него дочерний NumD2. В нем мы можем переопределить метод inc() и не можем сделать это с dec(), потому что в NumDouble объявили метод финальным.

Бывает так, что код переопределяемого метода не сильно отличается от того, что используется в родительском классе. Нам нужно его лишь дополнить. В этом случае из тела метода дочернего класса, вызывается родительский метод через слово super. После того, как он возвращает поток выполнения в метод дочернего класса, здесь выполняется дополнительный код. Подобное мы уже видели, переопределяя toString().

Давайте сделаем это в нашем классе NumDouble, хотя в данном случае большого смысла в этом нет. Тело класса очень простое.

open class NumDouble(n: Int, gap: Int): NumInc(n, gap) {
 
    override fun inc() {
        super.inc()
        number += step
    }
 
    override fun dec() {
        super.dec()
        number -= step
    }
}

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

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

open class NumInc {
    open var number = 0
    open var step = 1
 
    open fun inc() {number += step}
    open fun dec() {number -= step}
}
 
open class NumDouble: NumInc() {
    override var number = 1
    override var step = 2
}

Практическая работа:

Пусть свойство step родительского класса NumInc имеет сеттер, проверяющий число на неравенство нулю (нулевой шаг недопустим). В дочернем классе NumDouble такая проверка не нужна. Наследуется ли сеттер, следует ли его переопределять?

PDF-версия курса с ответами к практическим работам


Какая необходимость переопределять методы equals и hashCode в Java

1 ответ на этот вопрос.

0 голосов

Вы должны переопределить hashCode() в каждом классе, который переопределяет equals(). Невыполнение этого требования приведет к нарушению общего контракта для Object.hashCode(), что не позволит вашему классу правильно функционировать в сочетании со всеми коллекциями на основе хэшей, включая HashMap, HashSet и Hashtable.

— from  Effective Java , Джошуа Блох

Определив equals() и hashCode(), вы можете повысить удобство использования ваших классов в качестве ключей в коллекциях на основе хэшей. Как поясняется в документе API для hashCode: «Этот метод поддерживается для хеш-таблиц, таких как те, которые предоставляются java.util.Hashtable».

ответил 2 января 2019 г. к Дейзи • 8 120 баллов

Связанные вопросы в Java

Оба ответа (Синтаксис) верны. Если … ПОДРОБНЕЕ

ответил 5 июня 2018 г. на Яве к Акрати • 3190 баллов • 553 просмотра

  • ява
  • java-перечисление
  • Java-программирование

Список — это упорядоченная последовательность элементов. … ПОДРОБНЕЕ

ответил 26 апр. 2018 г. на Яве к Акрати • 3,190 баллов • 58 036 просмотров

  • ява
  • набор
  • список

Чтение json из URL-адреса с помощью url.openStream() и чтение содержимого … ПОДРОБНЕЕ

ответил 13 июня 2018 г. на Яве к самарт395 • 2220 баллов • 4580 просмотров

  • ява
  • Java-JSON

В Java геттеры и сеттеры полностью … ПОДРОБНЕЕ

ответил 26 июня 2018 г. на Яве к Скарлетт • 1,290 баллов • 1609 просмотров

  • ява
  • геттеры
  • сеттеры

Насколько я знаю, основной. .. ПОДРОБНЕЕ

ответил 19 июня 2018 г. на Яве к Скарлетт • 1,290 баллов • 9679 просмотров

  • ява
  • полиморфизм
  • приоритет
  • перегрузка

equals() должен определить эквивалентное отношение и … ПОДРОБНЕЕ

ответил 21 июня 2018 г. на Яве к Сушмита • 6,910 баллов • 337 просмотров

  • ява
  • приоритет

Вы не можете переопределить частный или статический … ПОДРОБНЕЕ

ответил 31 июля 2018 г. на Яве к код.reaper12 • 3 500 баллов • 10 427 просмотров

  • ява
  • функции-в-java
  • статический метод
  • приоритет

Большую часть времени вам нужно … ПОДРОБНЕЕ

ответил 14 декабря 2018 г. на Яве к Дейзи • 8 120 баллов • 1840 просмотров

  • ява
  • андроид
  • приоритет

Вы можете использовать этот метод: String[] strs = . .. ПОДРОБНЕЕ

ответил 25 июля 2018 г. на Яве к самарт395 • 2220 баллов • 2774 просмотра

  • ява
  • массив
  • Java-массив
  • Java-программирование
  • декларация Java-массива

Всякий раз, когда вам нужно изучить конструктор … ПОДРОБНЕЕ

ответил 23 августа 2018 г. на Яве к Дейзи • 8 120 баллов • 3247 просмотров

  • ява
  • java-строки
  • функции-в-java
  • Когда использовать promise.all()? 12 декабря 2022 г.
  • Как найти простые множители целого числа в JavaScript? 12 декабря 2022 г.
  • Создание PDF-файла из HTML в div с использованием Javascript 9 декабря 2022 г.
  • Перебор массива в JavaScript 9 декабря 2022 г.
  • Как получить текущее время только в JavaScript 9 декабря 2022 г.
    9 0173 Все категории
  • ЧатGPT (11)
  • Апач Кафка (84)
  • Апач Спарк (596)
  • Лазурный (145)
  • Большие данные Hadoop (1907)
  • Блокчейн (1673)
  • С# (141)
  • С++ (271)
  • Консультирование по вопросам карьеры (1060)
  • Облачные вычисления (3469)
  • Кибербезопасность и этичный взлом (162)
  • Аналитика данных (1266)
  • База данных (855)
  • Наука о данных (76)
  • DevOps и Agile (3608)
  • Цифровой маркетинг (111)
  • События и актуальные темы (28)
  • IoT (Интернет вещей) (387)
  • Джава (1247)
  • Котлин (8)
  • Администрирование Linux (389)
  • Машинное обучение (337)
  • Микростратегия (6)
  • PMP (423)
  • Power BI (516)
  • Питон (3193)
  • РПА (650)
  • SalesForce (92)
  • Селен (1569)
  • Тестирование программного обеспечения (56)
  • Таблица (608)
  • Таленд (73)
  • ТипСкрипт (124)
  • Веб-разработка (3002)
  • Спросите нас о чем угодно! (66)
  • Другие (2231)
  • Мобильная разработка (395)
  • Пользовательский интерфейс UX-дизайн (24)

Подпишитесь на нашу рассылку новостей и получайте персональные рекомендации.

Уже есть учетная запись? .

Почему вы должны переопределять Equals и HashCode в Java?

  Все хэш-теги

#java#springboot#react#angular#webservices#spring#javascript

 

 

902 71
LearninJava
11 января 2015 г. — Java

#java#springboot#react#angular#webservices#spring#javascript

 

Почему вы должны переопределить ?

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

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

Поиск объекта представляет собой двухэтапный процесс:

  • Поиск ведра с объектом
  • Сравнение объектов в ведре с объектом, который мы пытаемся найти в котором находится объект. Переопределение метода equals() предоставляет способ сравнить и увидеть, является ли объект, который мы находим, одинаковым или нет.

  • Вам нужен способ создать тот же объект, который вы хотите найти
  • hashCode() помогает нам найти правильный сегмент
  • equals() помогает нам сравнивать объекты в корзине
  • Во-первых, давайте посмотрим на приведенный ниже пример. Здесь мы создаем 3 объекта AngryBird разных цветов и добавляем их в HashSet. Теперь наша цель — найти КРАСНУЮ птицу. На данный момент мы не будем переопределять метод hashCode(). Давайте запустим пример и посмотрим вывод:

     

    EqualsAndHashCode.java : — Без переопределения hashCode 

    loading. ..

    Вывод:

    HashCode КРАСНОЙ птицы, которую нужно найти: 1

    HashCode птиц в ведрах..
    Цвет: ЧЕРНЫЙ HashCode: 2018699554

    Цвет: RED HashCode: 3667126 42

    Цвет: СИНИЙ HashCode: 1829164700

    КРАСНЫЙ сердитая птица присутствует в ведрах? : false

    Из вывода, если мы не переопределим метод hashCode(), КРАСНАЯ птица, которую нужно найти, имеет другой хэш-код, чем КРАСНАЯ птица в HashSet. Это означает, что в HashSet нет КРАСНОЙ злой птицы, что неверно. Теперь давайте переопределим hashCode() и посмотрим на результат. Ниже приведен обновленный пример,

     

    EqualsAndHashCode.java : — С переопределенным хэш-кодом равным нулю найти : 0

    HashCodes птиц в ведрах..
    Цвет : КРАСНЫЙ Хэш-код: 0

    Цвет: СИНИЙ Хэш-код: 0

    Цвет: ЧЕРНЫЙ Хэш-код: 0

    Присутствует ли в ведрах КРАСНАЯ злая птица? : правда

    Ааа! После переопределения hashCode() мы теперь видим, что хэш-коды КРАСНЫХ злых птиц совпадают. Также обратите внимание, что теперь мы можем найти объект в HashSet.

    Мы закончили? Пока нет, обратите внимание, что теперь у нас есть один и тот же хэш-код для всех злых птиц. Это не является нарушением и не причиняет никакого вреда. Если вы не знаете, как переопределить hashCode(), просто верните ноль, как показано.

    Однако эффективный алгоритм hashCode должен равномерно распределять объекты в корзинах. Чтобы понять это, мы проанализируем с помощью диаграммы, но перед этим давайте переопределим hashCode с помощью лучшего алгоритма. Вот как мы можем это сделать:

     

    EqualsAndHashCode.java : — С измененным и эффективным хэш-кодом 

    loading…

    Вывод:

    HashCode КРАСНОЙ птицы, которую нужно найти: 1

    HashCo des птиц в ведрах..
    Цвет: КРАСНЫЙ HashCode: 1

    Цвет: СИНИЙ Хэш-код: 2

    Цвет: ЧЕРНЫЙ Хэш-код: 3

    Присутствует ли в ведрах КРАСНАЯ злая птица? : true

    Теперь, поскольку hashCode возвращает размер птицы, а не только ноль, обратите внимание, что каждая птица имеет разный хэш-код в зависимости от ее размера. В чем преимущество с этим?

    Давайте посмотрим на диаграмму ниже и посмотрим, что произошло с кучей в каждом из вышеперечисленных случаев.

    Число в квадратных скобках — это хэш-код. Птица за пределами ведра — это та, которую мы хотим создать в качестве ключа и сопоставить ее с птицей в ведре.

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

    Надеюсь, после прочтения этой статьи ваши поиски hashCode и equals закончатся… 🙂

    Вот и все, ребята !! Удачного кодирования. Если вы считаете, что это помогло вам, продолжайте поддерживать нас по телефонам

    ,

    или

    ниже или в статьях в социальных сетях.

    •     Предыдущая статья Пользовательские и демонические потоки
    • Следующая статья    
      Состояния потоков — жизненный цикл потока
    90 274 ​​   Загрузить источник

    #java#springboot#react#angular#webservices#spring#javascript

     

    Нравится нам:

     

     

    Статьи по теме

    Java 8 — Stream

    В Java 8 появился новый концепция под названием Stream.