Что такое equals java
Сразу же скажу, что статья во многом опирается базовые понятия алгебры, которые к великому счастью легко и быстро осознаются при помощи всего-лишь метода внимательного разглядывания. Поехали.
В Java так устроено, что любой класс, который вы определяете, наследуется от класса Object. Таким образом класс Object является суперклассом любого класса в любой программе.
Прежде всего я должен описать главные правила для любых реализаций этих двух методов, которые нужно обязательно соблюдать, запомнить как аксиому:
1). Если x.equals(y) == true, то обязательно hashcode(x) == hashcode(y)
2) Если hashcode(x) == hashcode(y), то не обязательно x.equals(y) == true
Отношение эквивалентности (алгебра)
симметричным (для любых x, y выполняется: если x = y, то y = x)
рефлексивным (для любого x выполняется: x = x)
транзитивным (для любых x, y, z выполняется: если x = y и y = z, то x = z)
Метод .equals() в классе Object реализован примерно следующим образом:
Фактически он делает следующее: Он принимает в качестве аргумента ссылочную переменную и проверяет, ссылается ли они на тот же объект (ту же область памяти, если быть точнее), что и объект, к которому мы применили метод .equals().
Таким образом, стандартная реализация .equals() выстраивает отношение эквивалентности, которое можно описать так: две ссылки эквивалентны, если они ссылаются на одну и ту же область памяти.
Очевидно, гораздо более применимой будет возможность сравнивать объекты по какому-нибудь другому критерию. Часто метод .equals() переопределяют так, чтобы он сравнивал объекты по значениям их полей.
К примеру, если классы двух объектов, на которые указывают ссылки, совпадают и все значения их полей совпадают, то эти два объекта эквивалентны между собой. Легко проследить, что такое определение не противоречит математической идеологии.
Конкретную кодовую реализацию я приводить не буду, потому что она не так важна, как сама идея
Это и другие возможные переопределения метода .equals() мало того, что расширяют круг наших возможностей, так ещё и не лишают старых, ведь мы по прежнему имеем возможность проверять, ссылаются ли две ссылки на одну область памяти, используя операнд ==, вместо прежнего .equals()
Сюръекция (алгебра)
Если немного более подробно разобрать это определение, то мы увидим следующее:
Даже несколько элементов из X могут быть сопоставлены одному и тому же элементу из Y (это называется коллизией).
Возможно есть такое элемент из X, и даже возможно не один, что он не сопоставлен никакому элементу из Y. (см. рисунок, всё интуитивно)
Что происходит в java?
Метод .hashcode() как-раз осуществляет сюръекцию. Множеством X выступает множество всевозможных объектов которые мы можем создать, множеством Y выступает область значений типа данных int. Метод .hashcode() вычисляет каким-то скрытым от нас способом целое число, опираясь на объект, к которому применяется.
Единственное отличие метода .hashcode() от сюръекции в том, что любой объект может быть обработан методом .hashcode()
Здесь нет элементов по типу E из пред. рисунка
Насколько я понял, точно так никто в этом и не разобрался. Есть много версий:
Сама функция написана не на Java а вообще на C.
И многие другие. В общем каким-то образом она всё же устроена, но самое главное в том, что стандартная реализация .hashcode() со стандартной реализацией .equals() подчиняются правилу, приведённому в самом начале статьи
Основной причиной для изменения метода .hashcode() является то, что желают изменить .equals(), однако смена стандартной реализации .equals() приводит к нарушению правила из начала статьи
Методы equals & hashCode: зачем, где используются, как работают
— Теперь я расскажу о не менее полезных методах equals(Object o) & hashCode().
Как ты уже, наверное, успел запомнить, в Java при сравнении ссылочных переменных сравниваются не сами объекты, а ссылки на объекты.
Код | Пояснение | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
i не равно j Переменные указывают на различные объекты. Хотя объекты содержат одинаковые данные; | |||||||||||||||||||||||||||||||||||||||||||
Пример вызова: |
---|
Дробь one = new Дробь(2,3); Дробь two = new Дробь(4,6); System.out.println(one.equals(two)); |
Результат вызова будет true. дробь 2/3 равна дроби 4/6 |
— Для большей ясности я использовала русские названия. Так можно делать только в обучающих целях.
Теперь разберем пример.
Мы переопределили метод equals, и теперь для объектов класса Дробь у него будет своя реализация.
В этом методе есть несколько проверок:
1) Если переданный для сравнения объект – null, то объекты не равны. Объект, у которого вызвали метод equals ведь точно не null.
2) Проверка на сравнение классов. Если объекты разных классов, то мы не будем пробовать их сравнить, а сразу скажем, что это различные объекты – return false.
3) Со второго класса школы все помнят, что дробь 2/3 равна дроби 4/6. А как это проверить?
2/3 == 4/6 |
---|
Умножим обе части на оба делителя (6 и 3), получим: |
6 * 2 == 4 * 3 |
12 == 12 |
Общее правило: |
Если a / b == c / d То a * d == c * b |
— Поэтому в третьей части метода equals мы преобразуем переданный объект к типу Дробь и сравниваем дроби.
— Понятно. Если бы мы просто сравнивали числитель с числителем и знаменатель со знаменателем, то дробь 2/3 не была бы равной 4/6.
Теперь понятно, что ты имела ввиду, когда говорила, что только разработчик класса знает, как правильно его сравнивать.
— Да, но это только половина дела. Есть еще второй метод – hashCode()
— С методом equals все понятно, а зачем нужен hashCode()?
— Метод hashCode нужен для быстрого сравнения.
У метода equals есть большой минус – он слишком медленно работает. Допустим, у тебя есть множество(Set) из миллиона элементов, и нам нужно проверить, содержит ли оно определенный объект или нет. Как это сделать?
— Можно в цикле пройтись по всем элементам и сравнить нужный объект с каждым объектом множества. Пока не найдем нужный.
— А если его там нет? Мы выполним миллион сравнений, чтобы узнать, что там нет этого объекта? Не многовато ли?
— Да, даже мне понятно, что слишком много сравнений. А что, есть другой способ?
— Да, для этого и используется hashCode().
Метод hashCode() для каждого объекта возвращает определенное число. Какое именно – это тоже решает разработчик класса, как и в случае с методом equals.
Давай рассмотрим ситуацию на примере:
Представь, что у тебя есть миллион 10-тизначных чисел. Тогда в качестве hashCode для каждого числа можно выбрать остаток от его деления на 100.
Число | Наш hashCode |
---|---|
1234567890 | 90 |
9876554321 | 21 |
9876554221 | 21 |
9886554121 | 21 |
— Да, с этим понятно. И что нам делать с этим hashCode-числом?
— Вместо того чтобы сравнивать числа, мы будем сравнивать их hashCode. Так быстрее.
И только если hashCode-ы равны, сравнивать уже посредством equals.
— Да, так быстрее. Но нам все равно придется сделать миллион сравнений, только уже более коротких чисел, а для тех чисел, чьи hashCode совпадают, опять вызвать equals.
— Нет, можно обойтись гораздо меньшим числом.
Представь, что наше множество хранит числа, сгруппированные по hashCode или отсортированные по hashCode (что равносильно их группировке, т.к. числа с одинаковым hashCode находятся рядом). Тогда можно очень быстро и легко отбросить ненужные группы, достаточно один раз для каждой группы проверить совпадает ли ее hashCode с hashCode заданного объекта.
Представь, что ты студент, и ищешь своего друга, которого знаешь в лицо и про которого известно, что он живет в 17 общаге. Тогда ты просто проходишься по всем общежитиям универа и в каждом общежитии спрашиваешь «это 17 общага?». Если нет, то ты отбрасываешь всех из этой общаги и переходишь к следующей. Если «да», то начинаешь ходить по всем комнатам и искать друга.
В данном примере номер общаги – 17 – это и есть hashCode.
Разработчик, который реализует функцию hashCode, должен знать следующие вещи:
А) у двух разных объектов может быть одинаковый hashCode (разные люди могут жить в одной общаге)
В) хеш-коды должны быть выбраны таким образом, чтобы не было большого количества различных объектов с одинаковыми hashCode. Это сведет все их преимущество на нет (Ты пришел в 17 общагу, а там живет пол универа. Облом-с).
И теперь самое важное. Если ты переопределяешь метод equals, обязательно нужно переопределить метод hashCode(), с учетом трех вышеописанных правил.
В нашем примере с Дробью, если бы мы взяли hashCode равный числителю, то дроби 2/3 и 4/6 имели бы разные hashCode. Дроби – одинаковые, equals говорит, что они одинаковые, но hashCode говорит, что они разные. И если перед сравнением с помощью equals сравнивать по hashCode, то получим что объекты разные и до equals просто не дойдём.
— А как правильно реализовать hashCode для дроби?
— Тут надо помнить, что одинаковым дробям обязательно должен соответствовать одинаковый hashCode.
Вариант 1: hashCode равен целой части от деления.
Для дроби 7/5 и 6/5 это будет 1.
Для дроби 4/5 и 3/5 это будет 0.
Но этот вариант плохо годится для сравнения дробей, которые заведомо меньше 1. Целая часть (hashCode) всегда будет 0.
Вариант 2: hashCode равен целой части от деления знаменателя на числитель.
Этот вариант подойдет для случая, когда значение дроби меньше 1. Если дробь меньше 1, значит перевернутая дробь больше 1. А если мы переворачиваем все дроби – это никак не скажется на их сравнении.
Итоговый вариант будет совмещать в себе оба решения:
Проверяем для дробей 2/3 и 4/6. У них должны быть равные hashCode:
Дробь 2/3 | Дробь 4/6 | |
---|---|---|
числитель / знаменатель | 2 / 3 == 0 | 4 / 6 == 0 |
знаменатель / числитель | 3 / 2 == 1 | 6 / 4 == 1 |
числитель / знаменатель + знаменатель / числитель | 0 + 1 == 1 | 0 + 1 == 1 |
— Спасибо, Элли, было действительно интересно.
Сравнение объектов
1. Сравнение объектов в Java
Объекты в Java можно сравнивать как по ссылке, так и по значению.
Сравнение ссылок
Код | Вывод на экран |
---|
Сравнение по значению
Однако часто можно встретить ситуацию, когда две переменные ссылаются на два разных, но идентичных объекта. Например, две строки, которые содержат одинаковый текст, но находятся в различных объектах.
Для определения идентичности разных объектов нужно использовать метод equals() Пример:
Код | Вывод на экран |
---|
Метод equals есть не только у класса String — он есть у вообще всех классов.
Даже у тех, которые вы только будете писать, и вот почему.
2. Класс Object
Код | Как будет на самом деле: |
---|
Метод | Описание |
---|---|
Сравнивает текущий объект и переданный объект | |
Возвращает hash-code текущего объекта |
Получается, что методы equals есть у абсолютно всех объектов и можно сравнивать между собой объекты разных типов, и это все будет отлично компилироваться и работать.
Код | Вывод на экран |
---|
3. Метод equals()
Унаследованный от класса Object метод equals() содержит самый простой алгоритм сравнивания текущего и переданного объектов — он просто сравнивает их ссылки.
Тот же эффект вы получите, если просто сравните переменные класса Person вместо вызова метода equals(). Пример:
Код | Вывод на экран |
---|
Однако у класса String сравнение работает по-другому. Почему?
Реализация метода equals()
В коде это будет выглядеть так:
В коде это будет выглядеть так:
Вот как будет выглядеть наш новый код:
Если переданный объект не типа Person
4. Сравнение двух объектов Person
Если переданный объект не типа Person
Операция приведения типа
Если переданный объект не типа Person
Операция приведения типа
Но и это еще не все.
Во-вторых, поле name вполне себе может быть равным null : тогда вызвать метод equals у него нельзя. Нужна дополнительная проверка на null :
Код четвертого сценария может выглядеть, например, так:
Если возрасты не равны,
сразу return false
5. Метод hashCode()
Представьте, что вы сортируете в алфавитном порядке список из тысяч слов, и вам нужно постоянно попарно сравнивать слова. А слова длинные, и букв в них много. В общем, такое сравнение будет идти очень долго.
Однако его можно ускорить. Допустим, слова начинаются на разные буквы: сразу же понятно, что они разные. Вот если они начинаются на одинаковые буквы, тогда гарантий нет: в дальнейшем слова могут оказаться и равными, и различными.
Метод hashCode() работает по похожему принципу. Если его вызвать у объекта, то он вернет некое число — аналог первой буквы в слове. Это число обладает такими свойствами:
Для большего понимания перепишем эти свойства относительно слов:
Последнее свойство и используется для ускоренного сравнения объектов:
Сначала у двух объектов вычисляются hash-code. Если эти hash-code разные, то объекты точно разные, и сравнивать их дальше не нужно.
А вот если hash-code одинаковые, придется все же сравнивать объекты с помощью equals.
Метод equals в java – реализация на примерах
Фундаментальным аспектом любого класса Java является его определение равенства. Он определяется методом равных классов, и для правильной реализации есть несколько вещей. Давайте определим их, чтобы правильно понимать метод equals в java.
Метод определяет, является ли объект Number, вызывающий метод, равным объекту, который передается в качестве аргумента.
Метод возвращает значение True, если аргумент не равен null и является объектом того же типа и с тем же числовым значением.
Существуют некоторые дополнительные требования для объектов Double и Float, которые описаны в документации API Java.
Обратите внимание, что реализация equals всегда означает, что hashCode также должен быть реализован!
Идентичность в сравнении с равенством в Java
Посмотрите на этот фрагмент кода:
[java]String some = «some string»;
String other = «other string»;[/java]
У нас две строки и они разные.
Теперь разберем эти:
String some = «some string»;
String other = some;
boolean identical = some == other;
Здесь мы имеем только один экземпляр String, some и other ссылаются на него. В Java some и other идентичны и, соответственно, identical есть true.
Теперь посмотрите на такой пример:
String some = «some string»;
String other = «some string»;
boolean identical = some == other;
Теперь some и other указывают на разные экземпляры и больше не идентичны, поэтому identical является ложным.
В терминах Java они равны, что проверяется с помощью equals:
String some = «some string»;
String other = «some string»;
boolean equal = some.equals(other);
Здесь equals есть true.
Идентификатор переменной (также называемый ссылочным равенством) определяется ссылкой, которую он содержит. Если две переменные содержат одну и ту же ссылку, они идентичны. Это проверяется с помощью ==.
Равенство переменной определяется значением, на которое оно ссылается. Если две переменные ссылаются на одно и то же значение, то они равны. Это проверяется с помощью equals.
Но что означает «то же значение»? По сути, именно реализация равных определяет “одинаковость”. Метод equals определен в Object, и так как все классы наследуются от него, у всех есть этот метод.
Реализация в Object проверяет identity (обратите внимание, что одинаковые переменные также равны), но многие классы переопределяют его чем-то более подходящим. Для строк, например, он сравнивает последовательность символов, а для дат он гарантирует, что оба указывают на один и тот же день.
Когда же стоит делать перегрузку метода equals? Это стоит делать только тогда, когда для вашего класса определено понятие логической эквивалентности, которая не совпадает с тождественностью объектов.
Например, для классов Integer и String данное понятие можно применить.
Переопределение метода equals позволяет использовать экземпляры класса в качестве ключей в некой схеме или элементов в неком наборе, имеющих необходимое и предсказуемое поведение.
Многие структуры данных используют Equals для проверки того, содержат ли они элемент.
Например:
List list = Arrays.asList(«a», «b», «c»);
boolean contains = list.contains(«b»);
Переменная contains имеет значение true, хотя “b” не идентичны, но они равны. (Здесь начинает работать hashCode.)
Для лучшего понимания работы equals в java рассмотрим такой пример. Допустим, мы сравниваем ноутбуки и считаем их равными, если они имеют одинаковые характеристики.
Одно свойство настолько тривиально, что его вряд ли стоит упоминать: каждая вещь равна самой себе.
Если есть 2 объекта: если одна вещь равна другой, другая также равна первой. Очевидно, если мой ноутбук равен вашему, ваш равен моему.
Если если у нас есть три вещи, первая и вторая равны, вторая и третья равны, то первая и третья также равны. Опять же, это очевидно на нашем примере с ноутбуками.
Мы просто рассмотрели некоторые основные алгебраические свойства отношений эквивалентности. Нет, подождите, не уходи! Это уже все, что нам нужно. Потому что любое отношение, которое имеет три свойства как выше, можно назвать равенством.
equals-это не что иное, как формализация того, что мы видели выше.
Метод equals реализует отношение эквивалентности для ненулевых ссылок на объекты:
Реализация метода equals
Для класса Person со строковыми полями firstName и lastName общий вариант для реализации equals:
Очень важно, что equals принимает Object! В противном случае возникает непредвиденное.
Например, предположим, что мы будем реализовывать equals (Person) так:
public boolean equals(Person person) <
return Objects.equals(firstName, person.firstName)
&& Objects.equals(lastName, person.lastName);
>
Посмотрим на простом примере:
Person elliot = new Person(«Elliot», «Alderson»);
Person mrRobot = new Person(«Elliot», «Alderson»);
boolean equal = elliot.equals(mrRobot);
equal принимает значение true, а теперь:
Person elliot = new Person(«Elliot», «Alderson»);
Object mrRobot = new Person(«Elliot», «Alderson»);
boolean equal = elliot.equals(mrRobot);
Теперь это ложь. Может, не совсем то, что мы ожидали.
Причина в том, что Java вызывает Person.equals(Object) (наследуется от объекта, который проверяет идентичность). Почему?
Стратегия Java в выборе метода основана не на типе среды выполнения параметров, а на его объявленном типе.Поэтому, если Mr Robot объявлен как объект, Java вызывает Person.equals(Object) вместо нашего Person.equals(Person).
Самопроверка
Равенство является фундаментальным свойством любого класса, и оно может в конечном итоге вызываться очень часто, например, в циклах. Таким образом, его производительность имеет значение! И самоконтроль в начале нашей реализации – это просто оптимизация производительности.
java if (this == o) возвращает true;
Может показаться, что он должен реализовывать рефлексивность, но проверки дальше были бы очень странными, если бы они не делали этого.
Проверка NULL значения.
Ни один экземпляр не должен быть равен null, поэтому здесь мы убедимся в этом. В то же время он защищает код от Nullpointerexception.
if (o == null)
return false;
Заключение
Мы выяснили разницу между идентичностью (должна быть одна и та же ссылка, проверена с помощью ==) и равенством (могут быть разные ссылки на «одно и то же значение»).
Средняя оценка / 5. Количество голосов:
Или поделись статьей
Видим, что вы не нашли ответ на свой вопрос.
О сравнении объектов с помощью методов Java equals() и hashcode()
Без equals() и hashcode() нам пришлось бы создавать громоздкие сравнения «if», сопоставляя каждое поле с объектом. Что сделало бы исходный код запутанным.
Переопределение equals() и hashcode() в Java
Ниже приведен метод equals() в классе Object. Метод проверяет, совпадает ли текущий экземпляр с ранее переданным объектом.
Если equals() и hashcode() не переопределены, вместо них вы увидите приведенные выше методы. В этом случае методы не выполняют задачу equals() и hashcode() — проверку, имеют ли два (или более) объекта одинаковые значения.
Сравнение объектов с помощью метода equals ()
Мы используем метод equals() для сравнения объектов в Java. Чтобы определить, совпадают ли два объекта, equals() сравнивает значения атрибутов объектов:
Во втором сравнении equals() проверяет, является ли переданный объект пустым, или его тип принадлежит к другому классу. Если это другой класс, то объекты не равны.
Наконец, equals() сравнивает поля объектов. Если два объекта имеют одинаковые значения полей, то объекты одинаковы.
Анализ сравнений объектов
Рассмотрим результаты этих сравнений в методе main(). Сначала мы сравниваем два объекта Simpson:
Объекты идентичны, поэтому результат будет true.
Затем снова сравниваем два объекта Simpson:
Сравним объект Simpson и экземпляр класса Object :
В этом случае результат будет false, потому что типы классов разные.
equals () или ==
Может показаться, что оператор == и метод equals() делают то же самое. Но на самом деле они работают по-разному. Оператор == сравнивает, указывают ли две ссылки на один и тот же объект.
Идентификация объектов с помощью hashcode ()
Мы используем метод hashcode() для оптимизации производительности при сравнении объектов. Выполнение hashcode() возвращает уникальный идентификатор для каждого объекта в программе. Что значительно облегчает реализацию.
Использование equals() и hashcode() с коллекциями
Интерфейс Set отвечает за то, чтобы в подкласс Set не было повторяющихся элементов. Ниже перечислены часто используемые классы, реализующие интерфейс Set:
В приведенном ниже примере для добавления нового элемента в объект HashSet используется метод add. Перед добавлением нового элемента HashSet проверяет, существует ли элемент в данной коллекции:
Если объект тот же, новый элемент не будет вставлен.
Хэш-коллекции
Рекомендации по использованию equals() и hashcode()
Для объектов, имеющих один и тот же уникальный идентификатор hashcode, нужно использовать только equals(). Не нужно выполнять equals(), когда идентификатор hashcode отличается.
Таблица 1. Сравнение хэш-кодов
Если сравнение hashcode() … | Тогда … |
возвращает true | выполнить equals () |
возвращает false | не выполнять equals () |
Этот принцип используется в коллекциях Set или Hash для повышения производительности.
Правила сравнения объектов
Таблица 2. Сравнение объектов с помощью hashcode()
Когда сравнение хэш-кодов возвращает… | метод equals() должен возвращать … |
True | true или false |
False | False |
Таблица 3. Сравнение объектов с помощью equals()
Когда метод equals() возвращает … | метод hashcode() должен возвращать… |
True | True |
False | true или false |
Выполните задание на использование equals() и hashcode()!
Для начала внимательно изучите приведенный ниже код:
Проанализируйте код, угадайте результат, а затем запустите программу. Ваша цель заключается в том, чтобы улучшить навыки анализа кода,усвоить основные концепции Java и сделать создаваемый код более эффективным. Выберите свой вариант, прежде чем проверять правильный ответ, который приведен ниже.
Что сейчас произошло? Понимание equals() и hashcode()
В первом сравнении equals() результат верен, потому что метод hashcode() возвращает одно и то же значение для обоих объектов.
Обратите внимание, что размер коллекции задан для хранения трех объектов Simpson. Рассмотрим это более подробно.
Первый объект в наборе будет добавлен в коллекцию:
Следующий объект также будет добавлен, поскольку имеет отличное от предыдущего объекта значение:
Последний объект Simpson имеет то же значение, что и первый. В этом случае объект не будет вставлен:
Объект overridenHomer использует другое значение хэш-кода из обычного экземпляра Simpson («Homer»). По этой причине этот элемент будет вставлен в коллекцию:
Ответ
Распространенные ошибки использования equals() и hashcode()
Что нужно помнить о equals() и hashcode()
Пожалуйста, опубликуйте ваши отзывы по текущей теме статьи. За комментарии, подписки, лайки, отклики, дизлайки огромное вам спасибо!
Дайте знать, что вы думаете по этой теме в комментариях. Мы крайне благодарны вам за ваши комментарии, дизлайки, отклики, подписки, лайки!