Ответ
В HashMap (и других реализациях Map) равенство ключей определяется строго по контракту методов equals() и hashCode().
Механизм работы:
- При добавлении пары
put(key, value)вычисляется хэш-код ключа (hashCode()). - По хэш-коду находится соответствующий бакет (корзина).
- Внутри бакета ключи сравниваются с помощью метода
equals().
Ключи считаются совпадающими, если:
(key1 == key2) || (key1.hashCode() == key2.hashCode() && key1.equals(key2))
На практике это означает, что equals() возвращает true.
Пример с проблемой:
class BadKey {
String id;
// НЕТ переопределения equals() и hashCode() -> используется Object.equals()
}
Map<BadKey, String> map = new HashMap<>();
BadKey k1 = new BadKey(); k1.id = "a";
BadKey k2 = new BadKey(); k2.id = "a";
map.put(k1, "value1");
map.put(k2, "value2"); // Разные объекты по equals() -> ДВА разных ключа в map!
System.out.println(map.size()); // Вывод: 2
Best Practice: Для кастомных объектов, используемых как ключи, обязательно корректно переопределять equals() и hashCode(), основываясь на одних и тех же значимых полях. Это гарантирует, что логически одинаковые ключи будут корректно находиться и перезаписываться в Map.
Ответ 18+ 🔞
Слушай, вот сидишь ты такой, пишешь на Java, думаешь — ну что тут сложного, HashMap там, put, get, ерунда. А потом бац — и у тебя в мапе два ключа, которые по логике должны быть одним ключом, а они, сука, оба лежат! И начинаешь чесать репу: "Какого хуя?"
А весь пиздец, чувак, в том, что HashMap — он не телепат, блядь. Он не смотрит на поля твоего объекта и не догадывается, что у тебя там id одинаковый. Он работает строго по контракту, как бухгалтер-педант, ёпта.
Как он, блядь, внутри ебётся:
- Ты суёшь в него ключ через
put(key, value). Он первым делом спрашивает у ключа: "Мужик, какой у тебя хэш-код?" — то есть вызываетhashCode(). - По этому хэш-коду он тыкает ключ в определённую корзину (бакет), как письмо в ячейку на почте.
- А потом в этой корзине он уже начинает сравнивать ключи через
equals(). Если находит совпадение — перезаписывает значение. Не находит — кладёт новую пару.
И ключи считаются одинаковыми, только если:
(key1 == key2) || (key1.hashCode() == key2.hashCode() && key1.equals(key2))
По сути, если equals() сказал true — всё, пиши пропало, ключ один.
А вот тебе классический пиздец, который всех накрывает:
class BadKey {
String id; // Поле есть, а толку?
// НЕТ переопределения equals() и hashCode() -> используется дефолтный от Object!
}
Map<BadKey, String> map = new HashMap<>();
BadKey k1 = new BadKey(); k1.id = "a";
BadKey k2 = new BadKey(); k2.id = "a"; // Та же самая строка, логически тот же ключ!
map.put(k1, "value1");
map.put(k2, "value2"); // А вот хуй там! Для HashMap это РАЗНЫЕ объекты!
System.out.println(map.size()); // Вывод: 2, ёбаный насос!
Почему? Да потому что Object.equals() сравнивает ссылки (==), а не содержимое. А k1 и k2 — это разные ссылки, хоть и с одинаковым id. Вот и получается два ключа, волнение ебать.
Золотое правило, которое надо выжечь на жопе: Если делаешь свой класс и хочешь им как ключом в HashMap тыкать — обязательно, блядь, переопределяй equals() и hashCode(), и делай это на основе одних и тех же полей! Чтобы если два объекта логически равны, то и хэш-код у них был одинаковый, и equals() возвращал true. Тогда HashMap не будет сходить с ума и всё будет работать как часы, а не как мартышлюшка с гранатой.