- What is hashcode and equals in java
- Методы .equals и .hashcode в Java. Отличия реализации по умолчанию от реализации на практике
- Метод .equals()
- Отношение эквивалентности (алгебра)
- Реализация .equals() по умолчанию
- Как и зачем переопределяют метод .equals()?
- Метод .hashcode()
- Сюръекция (алгебра)
- Что происходит в java?
- Реализация .hashcode() по умолчанию?
- Как и зачем переопределяют метод .hashcode()?
- Методы equals() и hashcode() в языке Java
- Метод equals() в Java
- Контракт equals() в Java
- Использование equals
- Метод hashcode() в Java
- Контракт hashCode() в Java
- Использование hashCode
- Почему equals и hashCode в Java переопределяются вместе
- 1. equals есть, hashCode нет
- 2. hashCode есть, equals нет
What is hashcode and equals in java
День добрый, уважаемые дамы и господа знатоки. Есть вопрос прикладного характера. В начале объясняется, что прежде сравнения нужно убедиться, что сравниваются объекты одного класса. Это логично и понятно. Соответствующая строка в коде дополняет сказанное. НО! Зачем тогда следующей строкой приводить проверяемый объект к классу объекта-эталона? Если мы и так знаем, что объекты одного класса, иначе до выполнения этой строки не дошло бы — метод прекращается после return, зачем ещё раз приводить их к одному классу? Выглядит дико и просто как пятое колесо. У велосипеда. @Override public boolean equals(Object o) < if (getClass() != o.getClass()) return false; Man man = (Man) o; // Вот это что?? Зачем. return dnaCode == man.dnaCode; >
@Override public boolean equals(Object o) < if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LuxuryAuto that = (LuxuryAuto) o; // а может добавить еще одну проверку ? if (this.hashCode() != o.hashCode()) return false; // например такую ? // тогда псевдо одинаковые обьекты будут не равны при не равных хешах if (manufactureYear != that.manufactureYear) return false; return dollarPrice == that.dollarPrice; >@Override public int hashCode()
последний пример вводит в тупик. При сравнении через equals, сначала проверяются hashCode’ы, если они не равны, то сравнение через equals не последует. В нашем примере как раз таки это и происходит. Как equals показал true, если хэши у них разные и до метода equals код не должен был дойти и сразу дать false?? Теперь поговорим о методе hashCode(). Зачем он нужен? Ровно для той же цели — сравнения объектов. Но ведь у нас уже есть equals()! Зачем же еще один метод? Ответ прост: для повышения производительности. Хэш-функция, которая представлена в Java методом hashCode(), возвращает числовое значение фиксированной длины для любого объекта. В случае с Java метод hashCode() возвращает для любого объекта 32-битное число типа int. Сравнить два числа между собой — гораздо быстрее, чем сравнить два объекта методом equals(), особенно если в нем используется много полей. Если в нашей программе будут сравниваться объекты, гораздо проще сделать это по хэш-коду, и только если они равны по hashCode() — переходить к сравнению по equals().
Методы .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()
Метод .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() в языке Java
Методы equals и hashCode в Java в чём-то очень схожи, и даже вместе генерируются средствами IDE, таких как IntelliJ IDEA. Что в них общего, каковы отличия, и что будет, если использовать только один из методов?
Метод 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 , в котором предусмотрены поля с должностью и зарплатой:
В переопределённом методе equals() обе переменные участвуют в проверке. Также вы всегда можете убрать ту переменную, которую не хотите проверять на равенство.
Java разработчик (проект по созданию системы класса IDM) АО «Гринатом» , , можно удалённо , По итогам собеседования
Зачастую метод 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 по умолчанию сравнивает указатели на объекты, определяя, ссылаются ли они на один и тот же объект. Следовательно, пример из предыдущего пункта по идее должен выполняться. Но мы по-прежнему не сможем найти наш объект в хэш-таблице.
Для успешного поиска объекта в хэш-таблице помимо сравнения хэш-значений ключа используется также определение логического равенства ключа с искомым объектом.