Методы equals() и hashcode() в языке Java
Разберём equals и hashCode в Java: поговорим о различии методов, контрактов, а также посмотрим на реальные примеры использования.
Методы equals и hashCode в Java в чём-то очень схожи, и даже вместе генерируются средствами IDE, таких как IntelliJ IDEA. Что в них общего, каковы отличия, и что будет, если использовать только один из методов?
- Метод equals()
- Контракт equals()
- Использование equals
- Метод hashcode()
- Контракт hashCode()
- Использование hashCode
- Почему equals и hashCode в Java переопределяются вместе
Метод equals() в Java
Как вы наверняка знаете, сравнение посредством == в Java сравнивает ссылки, но объекты таким образом не сравнить. Следующий пример подобного сравнения двух строк вернёт false :
public static void main(String[] args) < //false System.out.println(new String("Tproger") == new String("Tproger")); >
Пусть значения и одинаковы, но переменные String указывают на разные объекты.
Тут-то в игру и вступает метод equals() , предусмотренный в Java для сравнения именно объектов. Данный метод проверяет два объекта одного происхождения на логическую равность.
То есть, сравнивая два объекта, программисту необходимо понять, эквивалентны ли их поля. При этом необязательно все поля должны быть идентичными, поскольку метод equals() подразумевает именно логическое равенство.
Контракт equals() в Java
Используя equals , мы должны придерживаться основных правил, определённых в спецификации Java:
- Рефлексивность — x.equals(x) возвращает true .
- Симметричность — x.equals(y) y.equals(x) .
- Транзитивность — x.equals(y) y.equals(z) x.equals(z) .
- Согласованность — повторный вызов x.equals(y) должен возвращать значение предыдущего вызова, если сравниваемые поля не изменялись.
- Сравнение null — x.equals(null) возвращает false .
Использование equals
Предположим, у нас есть класс Programmer , в котором предусмотрены поля с должностью и зарплатой:
public class Programmer < private final String position; private final int salary; protected Programmer(String position, int salary) < this.position = position; this.salary = salary; >>
В переопределённом методе equals() обе переменные участвуют в проверке. Также вы всегда можете убрать ту переменную, которую не хотите проверять на равенство.
Зачастую метод equals в Java определяется вместе с hashCode, но здесь мы рассмотрим первый метод отдельно:
@Override public boolean equals(Object o)
Определим объекты programmer1 и programmer2 типа Programmer с одинаковыми значениями. При их сравнении с помощью == вернётся false , так как это разные объекты. Если же мы используем для сравнения метод equals() , вернётся true :
public class Main extends Programmer < protected Main(String position, int salary) < super(position, salary); >public static void main(String[] args) < Programmer programmer1 = new Programmer("Junior", 300); Programmer programmer2 = new Programmer("Junior", 300); //false System.out.println(programmer1 == programmer2); //true System.out.println(programmer1.equals(programmer2)); >>
А вот такой результат мы получим, если хотя бы одна переменная (обозначенное поле объекта) из метода equals() не совпадёт:
public class Main extends Programmer < protected Main(String position, int salary) < super(position, salary); >public static void main(String[] args) < Programmer programmer1 = new Programmer("Junior", 300); Programmer programmer2 = new Programmer("Middle", 300); //false System.out.println(programmer1 == programmer2); //false System.out.println(programmer1.equals(programmer2)); >>
Метод hashcode() в Java
Наконец, мы дошли до сравнения методов equals и hashCode в языке Java.
Фундаментальное отличие в том, что hashCode() — это метод для получения уникального целочисленного номера объекта, своего рода его идентификатор. Благодаря хешу (номеру) можно, например, быстро определить местонахождение объекта в коллекции.
Это число используется в основном в хеш-таблицах, таких как HashMap . При этом хеш-функция получения числа на основе объекта должна быть реализована таким образом, чтобы обеспечить равномерное распределение элементов по хэш-таблице. А также минимизировать возможность появления коллизий, когда по разным ключам функция вернёт одинаковое значение.
В случае Java, метод hashCode() возвращает для любого объекта 32-битное число типа int . Сравнить два числа между собой гораздо быстрее, чем сравнить два объекта методом equals() , особенно если в нём используется много полей.
Контракт hashCode() в Java
- Повторный вызов hashCode для одного и того же объекта должен возвращать одинаковые хеш-значения, если поля объекта, участвующие в вычислении значения, не менялись.
- Если equals() для двух объектов возвращает true , hashCode() также должен возвращать для них одно и то же число.
- При этом неравные между собой объекты могут иметь одинаковый hashCode .
Использование hashCode
Вернёмся к нашему классу Programmer . По-хорошему, вместе с equals() должен быть использован и метод hashCode():
public class Programmer < private final String position; private final int salary; protected Programmer(String position, int salary) < this.position = position; this.salary = salary; >@Override public boolean equals(Object o) < if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Programmer that = (Programmer) o; if (salary != that.salary) return false; return Objects.equals(position, that.position); >@Override public int hashCode() < int result = position != null ? position.hashCode() : 0; result = 31 * result + salary; return result; >>
Почему equals и hashCode в Java переопределяются вместе
Сперва производится сравнение по хешу, чтобы понять, совпадают ли объекты, а только после подключается equals , чтобы определить, совпадают ли значения полей объекта.
Рассмотрим два сценария.
1. equals есть, hashCode нет
С точки зрения метода equals два объекта будут логически равны, но по hashCode они не будут иметь ничего общего. Таким образом, помещая некий объект в хэш-таблицу, мы рискуем не получить его обратно по ключу:
Map m = new HashMap<>(); m.put(new Point(1, 1), "Point A"); //pointName == null String pointName = m.get(new Point(1, 1));
2. hashCode есть, equals нет
Метод equals по умолчанию сравнивает указатели на объекты, определяя, ссылаются ли они на один и тот же объект. Следовательно, пример из предыдущего пункта по идее должен выполняться. Но мы по-прежнему не сможем найти наш объект в хэш-таблице.
Для успешного поиска объекта в хэш-таблице помимо сравнения хэш-значений ключа используется также определение логического равенства ключа с искомым объектом.
Как вычисляется hashcode java
Hashcode в Java вычисляется с помощью метода hashCode() .
Он возвращает целочисленное значение, используемое для идентификации объекта. Для вычисления Java использует алгоритм хеширования, который преобразует данные объекта в целочисленное значение. Реализация алгоритма зависит от JVM.
public class App public static void main(String[] args) Object myObject = new Object(); System.out.println(myObject.hashCode()); // => 1995265320 String myStr = "Hello"; System.out.println(myStr.hashCode()); // => 69609650 Integer myInt = 5; System.out.println(myInt.hashCode()); // => 5 > >
Мы можем переопределить метод hashСode() для своих объектов. При этом необходимо учитывать следующее :
1) Согласно контракту методы hashCode() и equals() переопределяются одновременно.
2) Правила переопределения :
- вызов метода hashCode() один и более раз над одним и тем же объектом должен возвращать одно и то же хэш-значение, при условии что поля объекта, участвующие в вычислении значения, не изменялись.
- вызов метода hashCode() над двумя объектами должен всегда возвращать одно и то же число, если эти объекты равны (вызов метода equals() для этих объектов возвращает true).
- вызов метода hashCode() над двумя неравными между собой объектами должен возвращать разные хэш-значения. Хотя это требование и не является обязательным, следует учитывать, что его выполнение положительно повлияет на производительность работы хэш-таблиц.
3) Кроме того хороший хэшкод должен
- быстро вычисляться
- равномерно распределять значения для объектов класса
После переопределения первоначальный (идентификационный) хеш доступен через метод System.identityHashCode() .
Методы .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() является то, что желают изменить вероятность коллизии (эта причина встречается реже)
Конец 🙂
Как работает hashcode в java
Метод hashCode() в Java используется для получения уникального целочисленного значения, которое представляет объект. Это значение может быть использовано для оптимизации хэш-таблиц и других структур данных.
По умолчанию, метод hashCode() возвращает уникальное значение для каждого объекта, основываясь на его адресе в памяти. Но в большинстве случаев, особенно при работе с коллекциями, требуется иметь более предсказуемый способ вычисления хэш-кода
Для этого, классы должны переопределить метод hashCode() , чтобы он генерировал уникальный код на основе значимых полей объекта. Кроме того, при переопределении метода hashCode() , также необходимо переопределить метод equals() , чтобы он сравнивал значения этих полей.
Например, для класса Person со свойствами name и age , переопределение методов hashCode() и equals() может выглядеть так:
public class Person private String name; private int age; // constructors, getters, setters, etc. @Override public int hashCode() final int prime = 31; int result = 1; result = prime * result + age; result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; > @Override public boolean equals(Object obj) if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Person other = (Person) obj; if (age != other.age) return false; if (name == null) if (other.name != null) return false; > else if (!name.equals(other.name)) return false; return true; > >
В этом примере метод hashCode() использует константу 31 и вычисляет хэш-код объекта на основе его свойств name и age . Метод equals() сравнивает значения этих свойств.
Таким образом, переопределение метода hashCode() позволяет использовать объекты в хэш-таблицах , таких как HashMap и HashSet , а переопределение метода equals() — сравнивать объекты на равенство в этих структурах.
