- Java hashCode() and equals() Contract
- How does equals() work?
- How does hashCode() work?
- Interested in using Java for you projects?
- Методы equals() и hashcode() в языке Java
- Метод equals() в Java
- Контракт equals() в Java
- Использование equals
- Метод hashcode() в Java
- Контракт hashCode() в Java
- Использование hashCode
- Почему equals и hashCode в Java переопределяются вместе
- 1. equals есть, hashCode нет
- 2. hashCode есть, equals нет
- Что думаете?
Java hashCode() and equals() Contract
The topic of the equals() and hashCode() contract in Java is so important that its proper understanding is essential to implement correctly and efficiently working solutions. Poor implementation of the hashCode() and equals() methods can cause a lot of trouble for the applications you develop, which is why it is so important to explore this topic more extensively.
In this article, you will learn how to avoid bugs and performance problems when implementing hashCode() and equals() in your application.
How does equals() work?
Since every Java class inherits from the Object class, and Object implements the equals() method – every Java class by default implements equals(). The implicit implementation compares objects by their reference, and this is often not the right solution.
public boolean equals(Object obj)
In practice, the above method will only return true for objects stored in the same memory location. Whereas when calling the equals() method our intention should be to compare the values of the objects. Let’s consider following example:
class Car < private final String concern; private final String model; private final int productionYear; public Car(String concern, String model, int productionYear) < this.concern = concern; this.model = model; this.productionYear = productionYear; >> @Test void shouldBeEqualIfAllValuesAreTheSame()
The above test will fail, even though each value of both Car instances is the same. This is because each object in Java created with a new keyword is stored in a different memory location. Therefore, in order to correctly compare the values of objects, the equals() method should be overridden.
For this purpose, you can use the IDE or code-generating libraries, keeping in mind a few important things that we have mentioned in his article: Pitfalls of automatically generated code.
@Override public boolean equals(Object o) < if (this == o) < return true; >if (o == null || getClass() != o.getClass()) < return false; >Car car = (Car) o; if (productionYear != car.productionYear) < return false; >if (!Objects.equals(concern, car.concern)) < return false; >return Objects.equals(model, car.model); >
Overriding equals() method, as indicated above, will cause the shouldBeEqualIfAllValuesAreTheSame() test to pass correctly. To implement correct equals() method, following things need to be fulfilled:
- It has to be reflexive, so for any non-null value, x.equals(x) should return true.
- It has to be symmetric, so for any non-null value when x.equals(y) returns true, then y.equals(x) has to return true as well.
- It has to be transitive, so for any non-null value if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) has to return true.
- It has to be consistent, so multiple invocations of x.equals(y) have to return the same value for each call.
- It has to return false for any non-null value calling x.equals(null).
How does hashCode() work?
The most important thing to remember about hashCode() is that each time we override equals() implementation we have to override hashCode() as well. This is essential for the correct operation with the hash-based collections. Let’s assume that we have our current implementation of Car.java, but we didn’t override hashCode() yet. What will happen when we try to work with HashMap?
@Test void shouldContainOnlyOneKeyWhenAddingEqualCars()
The values in the hashMap should be unique. When adding a new entry to a HashMap, firstly hashCode() is computed and based on its value the entry is added to the proper bucket. Then the new object is compared with other objects inside the bucket with the equals() method.
If equals() returns true, then the new object replaces the old one and if equals() returns false, then the new object is simply added to the bucket alongside the other objects. That is why the correct implementation of the hashCode() method is so important.
The above test will fail, because cars keySet will contain both keys: car1 and car2 as hashCode() will return different results, even though each value inside the objects are the same, while only car2 key should be left as it should replace car1. In order to pass the test correctly, the hashCode() method has to be overridden.
@Override public int hashCode()
Another important thing to remember is that when any value inside the object is changed, and this value is used inside the hashCode() method, then the result of hashCode() will also change. This is crucial when using an object as a key in HashMap.
Let’s assume that int productionYear is not final anymore, the setter method setProductionYear() is added to Car.java and we use Car instances as keys. In this case, changing the value will make it impossible to extract the value from the HashMap as shown in the test below.
@Test void shouldReturnNullIfValueChanged()
Following statements have to be fulfilled to provide correct implementation of the hashCode() method:
- It has to return the same result whenever it is invoked on the same object more than once during the runtime of application.
- If equals() returns true, then hashCodes of both objects have to be equal.
- If equals() returns false, then it is not required for hashCodes to be different for both objects, but it is reasonably practical and recommended.
- If hashCode() is different for objects, then equals() has to return false.
So what is the hashCode() equals() contract actually? Basically it is a set of rules that are already outlined above, and meeting them ensures the proper functioning of the application. HashCode is a kind of shortcut for an object that should be as unique as possible for optimization purposes, whereas equals() should check the equality of objects as precisely as possible.
Interested in using Java for you projects?
Check out our services and migrate with us to the next level
Методы 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() обе переменные участвуют в проверке. Также вы всегда можете убрать ту переменную, которую не хотите проверять на равенство.
Зачастую метод 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 по умолчанию сравнивает указатели на объекты, определяя, ссылаются ли они на один и тот же объект. Следовательно, пример из предыдущего пункта по идее должен выполняться. Но мы по-прежнему не сможем найти наш объект в хэш-таблице.
Для успешного поиска объекта в хэш-таблице помимо сравнения хэш-значений ключа используется также определение логического равенства ключа с искомым объектом.
Что думаете?
Ребят, тут собрались ноунеймы которые не работают ни на одном языке, но пишут свое очень важное мнение в комментариях. Лучше проходите мимо и не читайте их. Ах да, учите go и устройтесь в яндекс)
Как вы собираетесь искать хороших сотрудников, если (в большинстве компаний) честных кандидатов отметают даже не пригласив на техническое собеседование?Если умение лгать является обязательным, чтобы устроиться к вам на работу, то не удивляйтесь что «сложно найти хорошего сотрудника».Я знаю о чем говорю. В нашей компании для продвижения программистов на аутсорс есть целая отдельная команда, которая полностью специализируется на «продаже сотрудников». Это люди, которые пристально изучают хотелки чсв hr-ов, пишут «идеальные» резюме и отвечают на все вопросы так, «как надо». А программист приходит только на техническое собеседование в конце.Хорошие сотрудники (как правило) не станут накручивать себе 20 лет стажа, рассказывать про мотивацию «не ради денег», отвечать на глупые вопросы про квадратные люки и прочую ерунду.Вам нужно не учить людей в интернете «как правильно отвечать на наши вопросы, чтобы вы у нас прошли собес», а мыслить шире и заниматься реальным поиском толковых специалистов, которые не обязаны иметь топовые софт-скилы.В противном случае — получайте «идеальные» резюме, написанные по единому шаблону и котов в мешке. И не забудьте пожаловаться что «сложно найти хорошего сотрудника».
Читаю я комментарии и полностью убеждаюсь в том, почему так сложно найти хорошего сотрудника. Да, работодателю неприятно, когда соискатель отключает камеру, а на заднем фоне домашние едят. Неприятно, если человек сразу говорит, что на прошлой работе одни дураки. Настораживают люди, которые каждый год меняют работу и говорят «мало платят». Называть не по имени это вообще признак из серии » Ты, ходор, на фиг не сдался». И прочее. Но большинство комментариев как раз от людей с чсв. Из серии «любите меня любого, я вам одолжение делаю тем, что общаюсь».