Вопрос проверяет понимание контракта метода hashCode в Java и его роли в корректной работе коллекций, основанных на хешировании.
Метод hashCode() в Java является частью фундаментального контракта между equals() и hashCode(). Его основная цель — предоставить числовое значение (хеш-код), которое используется хеш-таблицами (например, HashMap, HashSet, Hashtable) для эффективного распределения объектов по «корзинам» (buckets) и их последующего быстрого извлечения.
Детерминированность означает, что при многократном вызове hashCode() для одного и того же объекта (при условии, что его состояние, используемое в вычислениях, не изменилось) должен возвращаться один и тот же результат. Это требование прямо указано в документации Java. Если оно нарушается, работа хеш-таблиц ломается.
Рассмотрим пример с HashMap:
HashMap вычисляется его хеш-код, чтобы определить индекс корзины, в которую он будет помещён.map.get(key)) снова вычисляется хеш-код, чтобы найти ту же корзину.HashMap будет искать объект в неверной корзине и не найдёт его, хотя объект физически присутствует в коллекции.Следующий класс имеет недетерминированный hashCode, который зависит от текущего времени. Это нарушает контракт и делает объект непригодным для использования в качестве ключа в HashMap.
public class BadKey {
private final String id;
public BadKey(String id) {
this.id = id;
}
@Override
public int hashCode() {
// НЕДЕТЕРМИНИРОВАННЫЙ КОД! Не делайте так.
// Хеш-код зависит от текущего времени, поэтому будет меняться.
return (int) (System.currentTimeMillis() % Integer.MAX_VALUE);
}
@Override
public boolean equals(Object obj) { ... } // корректная реализация
public static void main(String[] args) {
Map<BadKey, String> map = new HashMap<>();
BadKey key = new BadKey("key1");
map.put(key, "value1");
// Через мгновение hashCode ключа изменится.
System.out.println(map.get(key)); // С большой вероятностью выведет: null
}
}Правильная реализация hashCode() должна основываться на тех же полях экземпляра, что и метод equals(). Эти поля должны быть неизменяемыми (immutable) для ключевых объектов, либо разработчик должен гарантировать, что объект не будет использоваться в качестве ключа в хеш-таблице после изменения этих полей. Стандартный способ — использовать утилитный метод Objects.hash() или вычислять хеш на основе полей вручную.
public class GoodKey {
private final String name;
private final int id;
public GoodKey(String name, int id) {
this.name = name;
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GoodKey goodKey = (GoodKey) o;
return id == goodKey.id && Objects.equals(name, goodKey.name);
}
@Override
public int hashCode() {
// Детерминированная реализация на основе полей, используемых в equals.
return Objects.hash(name, id);
}
}Вывод: Детерминированность hashCode необходима для обеспечения корректности и предсказуемости работы всех структур данных, основанных на хешировании. Если вы создаёте класс, экземпляры которого могут использоваться в качестве ключей в HashMap или элементами в HashSet, реализуйте hashCode так, чтобы он зависел только от неизменяемых (или гарантированно не меняющихся в контексте использования) полей и всегда возвращал одно значение для одного состояния объекта.