Методы .equals и .hashcode в Java. Отличия реализации по умолчанию от реализации на практике
Сразу же скажу, что статья во многом опирается базовые понятия алгебры, которые к великому счастью легко и быстро осознаются при помощи всего-лишь метода внимательного разглядывания. Поехали.
В Java так устроено, что любой класс, который вы определяете, наследуется от класса Object. Таким образом класс Object является суперклассом любого класса в любой программе.
Это означает, что абсолютно любой класс содержит методы, которые определены в классе Object. Методы .equals() и .hashcode() — одни из них.
Прежде всего я должен описать главные правила для любых реализаций этих двух методов, которые нужно обязательно соблюдать, запомнить как аксиому:
1). Если x.equals(y) == true, то обязательно hashcode(x) == hashcode(y)
2) Если hashcode(x) == hashcode(y), то не обязательно x.equals(y) == true
Метод .equals()
Отношение эквивалентности (алгебра)
Прежде чем поговорить о методе .equals, я бы хотел рассказать, что такое отношение эквивалентности с точки зрения алгебры (пока-что забудьте про программирование).
Отношение эквивалентности — это бинарное (бинарное — значит между двумя) отношение, которое является:
Таким образом, если на множестве определено отношение эквивалентности, множество можно разделить на подмножества — классы эквивалентности.
Каждый класс эквивалентности содержит внутри себя только те элементы, которые эквиваленты (более формально — находятся в отношении эквивалентности) между собой.
Реализация .equals() по умолчанию
Метод .equals() в классе Object реализован примерно следующим образом:
public boolean equals(Object x)
Фактически он делает следующее: Он принимает в качестве аргумента ссылочную переменную и проверяет, ссылается ли они на тот же объект (ту же область памяти, если быть точнее), что и объект, к которому мы применили метод .equals().
Таким образом, стандартная реализация .equals() выстраивает отношение эквивалентности, которое можно описать так: две ссылки эквивалентны, если они ссылаются на одну и ту же область памяти.
Такая реализация не противоречит математической идеологии, описанной выше. Однако на практике метод .equals() часто переопределяют в подклассах.
Как и зачем переопределяют метод .equals()?
Очевидно, гораздо более применимой будет возможность сравнивать объекты по какому-нибудь другому критерию. Часто метод .equals() переопределяют так, чтобы он сравнивал объекты по значениям их полей.
К примеру, если классы двух объектов, на которые указывают ссылки, совпадают и все значения их полей совпадают, то эти два объекта эквивалентны между собой. Легко проследить, что такое определение не противоречит математической идеологии.
Конкретную кодовую реализацию я приводить не буду, потому что она не так важна, как сама идея
Это и другие возможные переопределения метода .equals() мало того, что расширяют круг наших возможностей, так ещё и не лишают старых, ведь мы по прежнему имеем возможность проверять, ссылаются ли две ссылки на одну область памяти, используя операнд ==, вместо прежнего .equals()
return(ob1 == ob2);
Метод .hashcode()
Сюръекция (алгебра)
Сюръекция — сопоставление элементам множества X элементов второго множества Y, при котором для любого элемента из Y есть хотя-бы один сопоставленный элемент из X.
Если немного более подробно разобрать это определение, то мы увидим следующее:
- Даже несколько элементов из X могут быть сопоставлены одному и тому же элементу из Y (это называется коллизией).
- Возможно есть такое элемент из X, и даже возможно не один, что он не сопоставлен никакому элементу из Y. (см. рисунок, всё интуитивно)
Что происходит в java?
Метод .hashcode() как-раз осуществляет сюръекцию. Множеством X выступает множество всевозможных объектов которые мы можем создать, множеством Y выступает область значений типа данных int. Метод .hashcode() вычисляет каким-то скрытым от нас способом целое число, опираясь на объект, к которому применяется.
Единственное отличие метода .hashcode() от сюръекции в том, что любой объект может быть обработан методом .hashcode()

Реализация .hashcode() по умолчанию?
Насколько я понял, точно так никто в этом и не разобрался. Есть много версий:
- Значение .hashcode() — это область памяти, где лежит объект
- Значение .hashcode() — это число, создаваемое генератором случайных чисел в какой-то момент
- Сама функция написана не на Java а вообще на C.
И многие другие. В общем каким-то образом она всё же устроена, но самое главное в том, что стандартная реализация .hashcode() со стандартной реализацией .equals() подчиняются правилу, приведённому в самом начале статьи
Как и зачем переопределяют метод .hashcode()?
Основной причиной для изменения метода .hashcode() является то, что желают изменить .equals(), однако смена стандартной реализации .equals() приводит к нарушению правила из начала статьи
Второстепенной причиной для изменения метода .hashcode() является то, что желают изменить вероятность коллизии (эта причина встречается реже)
Конец 🙂
Переопределение методов equals и hashcode: зачем?

Методы equals() и hashCode() являются основными методами класса Object в языке программирования Java. Метод equals() используется для сравнения двух объектов на их равенство, в то время как метод hashCode() используется для вычисления хеш-кода объекта.
В некоторых случаях, когда мы работаем с коллекциями в Java, такими как HashSet или HashMap, необходимо правильно реализовать методы equals() и hashCode() для наших собственных классов. Это связано с тем, что коллекции используют хеш-коды для определения правильного распределения элементов и быстрого поиска.
При добавлении объекта в коллекцию, сначала вычисляется хеш-код объекта с помощью метода hashCode(). Затем коллекция использует этот хеш-код для определения, в каком «ящике» (бакете) должен храниться объект. При поиске объекта коллекция сначала находит правильный бакет по его хеш-коду, а затем использует метод equals() для сравнения объекта внутри этого бакета с целевым объектом.
Таким образом, если мы переопределяем только один из этих методов, коллекция может работать неправильно. Например, если мы не переопределяем метод hashCode(), объекты могут быть добавлены в разные бакеты, что приведет к тому, что коллекция не сможет найти объект при его поиске. Если же мы не переопределяем метод equals(), коллекция может добавить дубликаты объектов в коллекцию, что противоречит ее предназначению.
Почему нужно переопределять equals и hashcode одновременно
Методы equals и hashcode в Java используются для работы с объектами и сравнения их значений. Они работают в паре и должны быть переопределены одновременно для корректной работы их использования.
Метод equals служит для сравнения двух объектов на их структурное равенство. Он имеет следующую сигнатуру:
- boolean equals(Object obj)
При переопределении метода equals необходимо учесть следующие правила:
- Рефлексивность: объект должен быть равен самому себе, то есть для любого объекта o, вызов o.equals(o) должен возвращать true.
- Симметричность: если объект A равен объекту B, то объект B также должен быть равен объекту A. То есть для любых объектов A и B, если A.equals(B) возвращает true, то и B.equals(A) должен возвращать true.
- Транзитивность: если объект A равен объекту B, и объект B равен объекту C, то объект A также должен быть равен объекту C. То есть для любых объектов A, B и C, если A.equals(B) и B.equals(C) возвращают true, то и A.equals(C) должен возвращать true.
- Согласованность: повторный вызов метода equals для одного и того же объекта должен всегда возвращать один и тот же результат, при условии, что никакая информация, используемая в equals, не изменилась.
- Проверка на null: метод equals не должен вызывать NullPointerException и должен возвращать false, если аргумент obj равен null.
Метод hashcode служит для генерации хэш-кода объекта. Он имеет следующую сигнатуру:
Правила переопределения метода hashcode:
- Если метод equals возвращает true для двух объектов, то и их hashcode должен быть равным. Однако, если два объекта имеют одинаковые hashcode, это не всегда означает, что они равны по equals.
- Метод hashcode должен быть определен таким образом, чтобы максимально равномерно распределять значения по всему диапазону int, чтобы уменьшить количество коллизий между объектами.
- Если переопределяется метод equals, то необходимо переопределить и метод hashcode, чтобы соблюсти требование, что равные объекты должны иметь одинаковый hashcode.
- Метод hashcode можно использовать для улучшения производительности при работе с коллекциями, так как многие коллекции используют хэш-коды внутри себя для эффективного поиска и сравнения объектов.
Переопределение методов equals и hashcode нужно для правильной работы методов сравнения и хэширования объектов. Несоблюдение правил переопределения может привести к неправильной работе коллекций, а также к появлению ошибок и неожиданному поведению вашего кода.
Проблемы сравнения объектов
При работе с объектами в Java часто возникает необходимость сравнения их между собой. Однако, стандартный метод сравнения equals() может не всегда работать так, как ожидается. Поэтому, для корректного сравнения объектов в Java необходимо переопределить методы equals() и hashCode() одновременно.
Такая необходимость возникает из-за того, что метод equals() используется во многих стандартных коллекциях и алгоритмах. Они могут основываться на значении хэш-кода объекта для оптимизации поиска или сравнения элементов коллекции. Поэтому, если мы не переопределим методы equals() и hashCode() , то может возникнуть некорректное поведение в работе таких алгоритмов.
Например, если мы создадим два объекта с одинаковыми значениями полей, то по умолчанию их хэш-коды будут разными. Поэтому, при попытке добавить эти объекты в HashSet, они будут считаться различными и оба будут добавлены в коллекцию. Такое поведение может быть неправильным и противоречить ожидаемым результатам.
Правильное переопределение методов equals() и hashCode() позволяет учесть только значимые поля объекта при сравнении и генерации хэш-кода. Это позволяет считать объекты с одинаковыми значениями полей, но разными ссылками, эквивалентными.
Также, несоответствие между методами equals() и hashCode() может привести к некорректному поведению в коллекциях, использующих хэширование, например, HashMap или HashSet. При попытке поиска объекта в такой коллекции при помощи метода contains() может быть получен некорректный результат из-за несоответствия хэш-кодов объектов или вызова метода equals() .
В целом, переопределение методов equals() и hashCode() является хорошей практикой при работе с объектами в Java. Оно позволяет учесть реальное сравнение значений полей объектов и использовать их в различных алгоритмах и коллекциях, обеспечивая корректное и предсказуемое поведение программы.
Важность сохранения целостности данных
Целостность данных является важным аспектом разработки программного обеспечения. Она гарантирует, что данные в системе остаются непротиворечивыми и корректными на протяжении всего процесса работы.
Сохранение целостности данных применяется во многих областях, включая базы данных, сетевое взаимодействие, криптографию и многое другое. Отсутствие механизмов проверки целостности может привести к непредсказуемым ошибкам и неправильному функционированию программы.
Одним из способов обеспечения целостности данных является переопределение методов equals и hashCode. Когда два объекта сравниваются с помощью метода equals, они считаются равными, если все их поля равны. Это позволяет избежать ситуации, когда разные объекты с одинаковыми данными считаются разными.
Однако, чтобы правильно использовать такое сравнение, необходимо также переопределить метод hashCode. Метод hashCode позволяет выполнять более эффективные проверки на равенство объектов, основываясь на значении хеш-кода. Реализация метода hashCode должна обеспечивать уникальность хеш-кода для каждого объекта, равные объекты должны иметь одинаковые хеш-коды.
Если методы equals и hashCode не переопределены, то объекты могут сравниваться по умолчанию, используя адрес памяти. Это может привести к неправильным результатам сравнения и причинить ошибки в работе программы.
Правильное переопределение методов equals и hashCode обеспечивает согласованность сравнения объектов. Это позволяет использовать объекты в коллекциях, таких как List, Set и Map, а также использовать их в сравнениях и проверках равенства.
В заключение, сохранение целостности данных является важным аспектом разработки программного обеспечения. Переопределение методов equals и hashCode помогает гарантировать правильное сравнение объектов и их корректное поведение в системе.
Вопрос-ответ
Зачем нужно переопределять методы equals и hashcode одновременно?
Переопределение методов equals и hashcode одновременно необходимо для правильного функционирования коллекций, которые основаны на хэш-таблицах, таких как HashSet или HashMap. Метод equals используется для сравнения объектов, а метод hashcode используется для определения индекса элемента в хэш-таблице. Если переопределение этих методов не происходит одновременно, то могут возникнуть ошибки при поиске и сравнении объектов в коллекции.
Какие проблемы могут возникнуть, если методы equals и hashcode не переопределены одновременно?
Если методы equals и hashcode не переопределены одновременно, то могут возникнуть проблемы при поиске и сравнении объектов в коллекции. Например, коллекции типа HashSet или HashMap будут некорректно работать, потому что нарушится принцип равенства объектов. Это может привести к тому, что один и тот же объект будет считаться разными объектами или наоборот. При использовании хэш-таблиц, такие ошибки могут привести к тому, что объект не будет найден в коллекции или будет найден некорректный объект.
Могу ли я переопределить только метод equals или только метод hashcode?
Да, вы можете переопределить только один из методов equals или hashcode. Однако, для корректной работы коллекций, основанных на хэш-таблицах, таких как HashSet или HashMap, рекомендуется переопределение обоих методов одновременно. Если один из методов не будет переопределен, то могут возникнуть ошибки при сравнении и поиске объектов в коллекции.
Можно ли использовать только метод equals для сравнения объектов?
Метод equals используется для сравнения объектов на равенство. Использование только метода equals может быть достаточным для некоторых ситуаций, где не требуется использование хэш-таблиц. Однако, для работы с коллекциями, основанными на хэш-таблицах, такими как HashSet или HashMap, необходимо переопределить и метод hashcode, чтобы обеспечить правильное функционирование коллекций.
Как правильно переопределить hashcode java
Переопределение hashCode в Java является важным шагом при реализации методов equals и hashCode для классов. Это необходимо для того, чтобы правильно хранить объекты в хэш-таблицах , например, в HashSet , HashMap и т.д.
Правильное переопределение hashCode в Java должно учитывать все поля, которые участвуют в методе equals , и должно использовать одинаковый алгоритм вычисления для всех объектов класса.
Пример правильного переопределения hashCode :
public class MyClass private String field1; private int field2; // constructor, getters, setters, etc. @Override public boolean equals(Object o) if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyClass myClass = (MyClass) o; return field2 == myClass.field2 && Objects.equals(field1, myClass.field1); > @Override public int hashCode() return Objects.hash(field1, field2); > >
В этом примере hashCode использует метод Objects.hash() , который генерирует хэш-код на основе переданных ему аргументов.
В аргументах перечислены все поля класса, которые участвуют в методе equals . Если вы добавите новое поле в класс, которое также должно участвовать в методе equals , не забудьте добавить его и в hashCode
Кроме того, при переопределении hashCode важно учитывать, что вычисленное значение хэш-кода не должно изменяться во время жизненного цикла объекта, то есть поля, участвующие в методе hashCode, должны быть неизменяемыми. В противном случае, если изменится значение поля объекта, хэш-код также изменится, что может привести к непредсказуемому поведению в хэш-таблицах.
equals() и hashCode() в Java или попугаи-неразлучники в ваших программах. Часть 2
В предыдущей части, если не читали вот она, мы подробно рассмотрели работу метода equals(), его контракт, ошибки и их исправления. Теперь настала очередь второго попугая-неразлучника – метода hashCode().
При переопределении метода equals() мы всегда должны переопределять метод hashCode(). Метод hashCode() – вычисляет целочисленное значение для конкретного элемента класса, чтобы использовать его для быстрого поиска и доступа к этому элементу в hash-структурах данных, например, HashMap, HashSet и прочих. Почему важно переопределять hashCode() всегда вместе с методом equals()? Развернуто ответим на этот вопрос. Пожалуй, необходимо и достаточно знать два важных аспекта, чтобы понять, почему необходимо делать переопределение методов вместе:
- Hash-код объекта используется для быстрой навигации в Hash – таблицах. Поэтому достаточно понять сам процесс поиска/вставки/удаления элемента в этих таблицах
- Связь equals() и hashCode(). Если два объекта o1 и o2 являются equals() (o1.equals(o2) = true), то они должны иметь одинаковый hash-код. Обратное – не обязательно.
Что такое хеш-таблицы (Hash Tables)?
Хэш – таблицы – это своего рода ассоциативный массив, хранящий значения в виде “ключ-значение”. Рассмотрим работу вставки элемента в хеш-таблицу:
- На входе мы получаем key – некий объект.
- Для объекта вызывается метод hashCode(), который вернет hash-код объекта – целое число.
- По этому числу мы находим соответствующий ему bucket – определенная структура, хранящая в себе все объекты с одинаковыми hash-кодом. В нашем случае это будет список.
- В найденный bucket мы записываем объект key.
На деле все просто, но если еще раз перечитать контракт hashCode() и equals(), то все становиться немного труднее: возможны коллизии – два разных объекта имеют одинаковый hash-код. Что делать? Эта проблема и ее решение отражены на рисунке выше. Два объекта John Smith и Sandra Dee имеют один и тот же hash-код. Для разрешения это коллизии мы просто берем за структуру bucket направленный список. И сохраняем два значения по одному hash-коду.
Как сломать хеш – таблицу?
При неверной реализации метода hashCode() мы можем легко сломать hash-таблицу. Вернее даже сказать не сломать, а сделать ее вырожденной. Например, переопределив метод hashCode() следующим образом
