Ответ
Контракт (соглашение), определенный в документации Java, гласит:
- Согласованность во времени: Если
equals()для двух объектов возвращаетtrue, то вызовhashCode()для каждого из них должен возвращать одно и то же целочисленное значение. При повторных вызовах на неизмененном объектеhashCode()должен возвращать то же значение. - Обратное не обязательно: Если
hashCode()для двух объектов совпадает, это не гарантирует, чтоequals()вернетtrue(это коллизия хэша).
Зачем это нужно?
Этот контракт критически важен для корректной работы hash-базированных коллекций: HashMap, HashSet, Hashtable. Они используют хэш-код для быстрого определения «корзины» (bucket), в которой может находиться объект.
Нарушение контракта приводит к ошибкам:
class BrokenPerson {
private String name;
private int age;
public BrokenPerson(String name, int age) { this.name = name; this.age = age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BrokenPerson that = (BrokenPerson) o;
return age == that.age && Objects.equals(name, that.name);
}
// НЕТ переопределения hashCode()! Используется реализация Object.hashCode()
}
// Проблема в использовании:
Set<BrokenPerson> set = new HashSet<>();
BrokenPerson p1 = new BrokenPerson("Alice", 30);
BrokenPerson p2 = new BrokenPerson("Alice", 30);
System.out.println(p1.equals(p2)); // true (логически равны)
set.add(p1);
System.out.println(set.contains(p2)); // Может вернуть FALSE! Объекты попали в разные корзины.
Правильная реализация:
Всегда переопределяйте hashCode(), если переопределяете equals(). Используйте одни и те же значимые поля в обоих методах. Удобно делать это с помощью Objects.hash().
class CorrectPerson {
private String name;
private int age;
public CorrectPerson(String name, int age) { this.name = name; this.age = age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CorrectPerson that = (CorrectPerson) o;
return age == that.age && Objects.equals(name, that.name); // Поля для equals
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Те же поля, что и в equals()!
}
} Ответ 18+ 🔞
А, слушай, смотри, вот эта вся хуйня с equals и hashCode в Java — это, блядь, не просто так придумали, это прям священный договор, контракт, который нарушать — себе дороже, в рот меня чих-пых!
Представь себе, сидит где-то в недрах Oracle какой-то ёбаный мудозвон и пишет в документации: «Ребята, договорились так, окей?». И договор вот какой:
- Согласованность, блядь. Если два объекта, по твоему мнению, равны (то есть
equals()говорит «да, сука, это одно и то же»), то и хэш-код у них обязан быть одинаковый. Один и тот же, блядь, цифровой отпечаток. И не меняться, пока объект не поменяли. - А вот наоборот — хуй там. Если хэш-коды совпали — это нихуя не значит, что объекты равны. Это просто коллизия, совпадение, типа как два разных человека могут жить в одном подъезде. Бывает, епта.
А нахуя это всё?
А нахуя? Да затем, что все эти твои HashMap, HashSet — они же не тупые, они хотят работать быстро. Они смотрят на хэш-код, чтобы понять, в какую «корзину» (bucket, блядь) запихнуть объект или где его искать. Это как индекс в книге, только для объектов.
А что будет, если наебать систему и нарушить контракт? Всё, пиздец, Колян. Всё сломается. Смотри, какой придурок-класс можно написать:
class BrokenPerson {
private String name;
private int age;
// ... конструктор и всё такое ...
@Override
public boolean equals(Object o) {
// Тут всё правильно, сравнивает по имени и возрасту
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BrokenPerson that = (BrokenPerson) o;
return age == that.age && Objects.equals(name, that.name);
}
// А hashCode() НЕ ПЕРЕОПРЕДЕЛИЛ! Используется дефолтный от Object!
}
И вот что получается, сука:
Set<BrokenPerson> set = new HashSet<>();
BrokenPerson p1 = new BrokenPerson("Alice", 30);
BrokenPerson p2 = new BrokenPerson("Alice", 30); // Та же самая, блядь, Алиса!
System.out.println(p1.equals(p2)); // true! Логически один человек!
set.add(p1);
System.out.println(set.contains(p2)); // А тут, ёпта, может выдать FALSE!
Почему? Да потому что p1 и p2, хоть и равные, имеют разные хэш-коды (дефолтные). HashSet посмотрел на хэш p2, пошёл не в ту корзину искать, и не нашёл нихуя. И сидит теперь, довольный, а программа не работает. Волнение ебать, терпения ноль ебать!
Как делать правильно, чтобы не быть мудаком?
Очень просто, блядь. Переопределил equals — тут же, не отходя от кассы, переопределяй и hashCode. И используй для расчёта хэша те же самые поля, что и для сравнения в equals. Не выёбывайся.
Вот, смотри, пример не мудака, а адекватного человека:
class CorrectPerson {
private String name;
private int age;
// ... конструктор ...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CorrectPerson that = (CorrectPerson) o;
return age == that.age && Objects.equals(name, that.name); // Поля для equals
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Блядь, ТЕ ЖЕ САМЫЕ ПОЛЯ! Идеально!
}
}
Вот так, коротко и ясно. Запомнил? equals и hashCode — как сиамские близнецы, ёпта. Где один, там и второй. Иначе получишь коллекцию, которая нихуя не работает, и будешь потом всю ночь дебажить, материться и кофе литрами хлебать. Сам от себя охуеешь.