Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.
Требования к реализации метода hashCode()
Объявление метода hashCode() выглядит следующим образом:
public int hashCode() {
// ...
}Для правильной реализации рассматриваемого метода в Java определен список следующих требований:
- Во-первых, если вызов метода
hashCode()выполняется неоднократно над одним и тем же объектом, то во всех случаях он должен возвращать одинаковое значение, учитывая, что над полями этого объекта не выполнялись какие-либо изменения. - Во-вторых, если вызов рассматриваемого метода выполняется над двумя равнозначными объектами, тогда он обязан возвращать одинаковый результат для обоих. Равенство таких объектов можно проверить благодаря методу
equals(). Он должен вернутьtrue, если сравниваемые объекты равны. - И наконец, если возвращаемые результаты при вызове метода для двух объектов одинаковы, то это не гарантирует их равенства. Если они разные, то это гарантирует, что объекты не равны.
Представленные выше требования говорят о важности использования рассматриваемого метода вместе с equals() для сравнения объектов. Второй выполняет сравнение объектов, а первый в свою очередь показывает, изменилось ли состояние объекта. Это еще раз подтверждает, что при переопределении метода equals() нужно не забывать про переопределение hashCode().
Переопределение метода hashCode()
Первое, что придет в голову пользователю, который захочет выполнить переопределение hashCode() в Java, – это сделать возвращение константы:
@Override
public int hashCode() {
return 7;
}Реализовывать переопределение подобным образом категорически нельзя, и на это есть свои причины:
- Данный пример реализации переопределения метода удовлетворяет требованиям и вернет одинаковое число для двух равных объектов, но в случае изменения состояния одного из них, его хэш-код не будет изменен. А это уже не соответствует требованиям, описанным выше.
- При подобной реализации возникновение коллизий гарантировано.
Исходя из вышеперечисленных недостатков, от подобного переопределения метода необходимо отказаться.
Существует два варианта правильного**** переопределения метода hashCode() в Java:
- Использование существующего алгоритма для собственной реализации переопределения.
- Использование вспомогательных методов для генерации хэш-кода.
Первый вариант предполагает выполнение следующих правил для переопределения hashCode() в Java:
- Во-первых, необходимо исключить все избыточные поля, которые не участвуют в
equals(). - Во-вторых, следует выбрать базу — стартовое число, необходимое для расчета hash-кода объекта и присвоить его переменной
total. Зачастую разработчики берут число 31, но вы можете выбрать иное значение. Многие IDE выполняют генерацию хэш-кода именно с этим числом. - Далее для каждого из оставшихся полей после исключения проводится расчет хэша. Ниже будет приведена таблица правил вычисления для возможных типов полей:
| Тип поля | Правило |
|---|---|
| boolean | (f ? 1 : 0) |
| char, short, byte или int | (int) f |
| float | Float.floatToIntBits(f) |
| double | Double.doubleToLongBits(f), а затем (int)(f ^ (f >>> 32)) |
| long | (int)(f ^ (f >>> 32)) |
| Ссылка на другой объект | Рекурсивный вывод метода hashCode() |
| Массив | Обработать каждый элемент массива, как отдельное поле объекта |
| null | return 0 |
char, short, byte или int
Double.doubleToLongBits(f), а затем (int)(f ^ (f >>> 32))
Рекурсивный вывод метода hashCode()
Обработать каждый элемент массива, как отдельное поле объекта
- Следующее правило гласит, что нужно прибавить рассчитанный хэш каждого из полей (допустим, это переменная
compute) к переменнойtotal:
total = 31 * total + compute;- И наконец, необходимо вернуть итоговое значение переменной
totalпосле выполнения всех расчетов.
Приведем пример реализации переопределения метода для класса Staff, используя перечисленные выше правила:
public class Staff {
private String FCs;
private String city;
private int experience;
private double wage;
private String department;
public Staff(String FCs, String city, int experience, double wage, String department) {
this.FCs = FCs;
this.city = city;
this.experience = experience;
this.wage = wage;
this.department = department;
}
@Override
public int hashCode() {
int total = 31;
total = total * 31 + (FCs == null ? 0 : FCs.hashCode());
total = total * 31 + (city == null ? 0 : city.hashCode());
total = total * 31 + experience;
long lwage = Double.doubleToLongBits(wage);
total = total * 31 + (int)(lwage ^ (lwage >>> 32));
total = total * 31 + (department == null ? 0 : department.hashCode());
return total;
}
// Переопределение equals()
// ...
}Аннотация @Override перед объявлением рассматриваемого метода проверяет, что переопределяемый метод есть в родительском классе.
Все строковые поля (FCs, city, и department) проверяются на null перед вызовом их метода hashCode(), чтобы обеспечить безопасность от NullPointerException.
Второй вариант предполагает использование вспомогательных методов для генерации хэш-кода, которые доступны благодаря классу java.util.Objects, начиная с версии Java 8+. Пример данной реализации приведен ниже:
@Override
public int hashCode() {
return Objects.hash(FCs, city, experience, department);
}У всех стандартных ссылочных типов данных в Java (String, Integer, Double и т. д.) методы equals() и hashCode() уже корректно переопределены. Поэтому их возможно спокойно интегрировать в коллекции HashMap, HashSet и другие.
Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.