Ответ
Контракт hashCode() — это набор правил, определённых в спецификации Java, которые должен соблюдать любой корректно переопределённый метод hashCode(). Эти правила обеспечивают правильную работу хэш-структур данных, таких как HashMap, HashSet и Hashtable.
Три основных правила контракта
- Консистентность (непротиворечивость): При многократных вызовах на одном и том же неизменённом (в рамках
equals) объекте метод должен стабильно возвращать одно и то же целое число. Значение может меняться между запусками приложения. - Согласованность с
equals(): Если два объекта равны согласноequals(Object), то вызовhashCode()для каждого из них должен возвращать одинаковое целое число. - Обратное не требуется: Разные объекты (по
equals) могут иметь одинаковые хэш-коды. Однако для производительности хэш-таблиц желательно, чтобы разные объекты возвращали разные хэш-коды (минимизирует коллизии).
Последствия нарушения контракта
- Нарушение правила 2 — критическая ошибка. Равные объекты с разными
hashCode()будут помещены в разные корзины (buckets)HashMap/HashSet, что приведёт к дублированию логически одинаковых элементов.
Пример корректной и некорректной реализации
import java.util.Objects;
// КОРРЕКТНАЯ реализация (соблюдает контракт)
class CorrectProduct {
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CorrectProduct)) return false;
CorrectProduct that = (CorrectProduct) o;
return Objects.equals(id, that.id); // equals по id
}
@Override
public int hashCode() {
return Objects.hash(id); // hashCode тоже по id
}
}
// НЕКОРРЕКТНАЯ реализация (нарушает контракт)
class BadProduct {
private Long id;
private String name;
@Override
public boolean equals(Object o) { /* сравнение по id и name */ }
// hashCode() НЕ ПЕРЕОПРЕДЕЛЁН! Используется нативный метод Object.hashCode(),
// который обычно возвращает разные значения для разных объектов.
// Равные по equals объекты будут иметь разные hashCode.
}
// Использование с ошибкой
BadProduct p1 = new BadProduct(1L, "Apple");
BadProduct p2 = new BadProduct(1L, "Apple");
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // false (скорее всего)
Set<BadProduct> set = new HashSet<>();
set.add(p1);
set.add(p2); // ОБА объекта добавятся в Set, хотя они равны!
Рекомендация: Всегда переопределяйте hashCode(), когда переопределяете equals(). Используйте Objects.hash(...) для удобной генерации хэш-кода по нескольким полям.
Ответ 18+ 🔞
А, ну вот, опять про эту мать их hashCode() с equals()! Сидишь такой, пишешь свой класс, думаешь — ну что тут сложного, поля сравню и всё. А потом, сука, в HashMap у тебя один объект есть, а другой, вроде бы такой же, не находится! И начинаешь ебать свой монитор, пока не вспомнишь про этот ёбаный контракт.
Так, слушай сюда, история про три заповеди, которые нельзя нарушать, иначе тебе пиздец в коллекциях.
Три священных правила, блядь:
- Стабильность, епта! Пока объект не меняется (в том смысле, что
equalsпро него говорит, что он тот же), егоhashCode()должен возвращать одно и то же число. Сегодня 42, завтра 42, после перезапуска приложения — может быть другое, но опять стабильное. Нельзя, чтобы он как мартышлюшка — то 10, то 500. - Согласие с
equals()— это святое! Еслиa.equals(b)вернулtrue, тоa.hashCode()иb.hashCode()обязаны быть одинаковыми. Это, блядь, альфа и омега. Без этого всё летит в пизду. - Обратное — не обязательно. Если хэш-коды совпали, это не значит, что объекты равны. Коллизии — это норма жизни, как пробки в час пик. Но если ты специально всем объектам будешь возвращать 1, то твоя
HashMapпревратится в ебаную связанную очередь, и производительность накроется медным тазом.
Что будет, если наебать с правилом номер два?
А будет вот что, чувак. Ты создашь два объекта, которые по логике твоего equals — одно и то же. Кинешь их в HashSet. А он, этот HashSet, нихуя не поймёт, что они одинаковые, потому что посмотрит на хэш-коды — а они разные! — и запихнёт оба. Получится у тебя дубликат, пиздец и позор. Коллекции начинают врать тебе в глаза, и доверия к ним — ноль ебать.
Смотри, как это выглядит в коде:
import java.util.Objects;
// Вот так надо делать, по-человечески
class NormalniyProduct {
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof NormalniyProduct)) return false;
NormalniyProduct that = (NormalniyProduct) o;
return Objects.equals(id, that.id); // Сравниваем только по id
}
@Override
public int hashCode() {
return Objects.hash(id); // И хэш тоже считаем ТОЛЬКО по id! Всё согласовано.
}
}
// А вот так делать — это просто пиздец какой-то, издевательство
class IdiotProduct {
private Long id;
private String name;
@Override
public boolean equals(Object o) { /* тут умное сравнение по id и name */ }
// А hashCode()? А НИХУЯ! Остался дефолтный от Object.
}
// Демонстрация пиздеца
IdiotProduct p1 = new IdiotProduct(1L, "Яблоко");
IdiotProduct p2 = new IdiotProduct(1L, "Яблоко");
System.out.println(p1.equals(p2)); // true, ёпта!
System.out.println(p1.hashCode() == p2.hashCode()); // false, с вероятностью 99.999%!
Set<IdiotProduct> set = new HashSet<>();
set.add(p1);
set.add(p2); // И ЧЁ? ОБА ДОБАВИЛИСЬ! SET С ДУБЛЯМИ! ДА ТЫ ОХУЕЛ?
Итог, блядь, простой: Не выёбывайся. Переопределил equals() — тут же, не отходя от кассы, переопределяй и hashCode(). И чтоб логика расчёта хэша использовала те же поля, что и equals. Всё, вопрос закрыт. Не хочешь париться — юзай Objects.hash(...), он за тебя всё аккуратно сделает.