Как в Java определяется равенство ключей в Map (HashMap)?

Ответ

В HashMap (и других реализациях Map) равенство ключей определяется строго по контракту методов equals() и hashCode().

Механизм работы:

  1. При добавлении пары put(key, value) вычисляется хэш-код ключа (hashCode()).
  2. По хэш-коду находится соответствующий бакет (корзина).
  3. Внутри бакета ключи сравниваются с помощью метода 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 одинаковый. Он работает строго по контракту, как бухгалтер-педант, ёпта.

Как он, блядь, внутри ебётся:

  1. Ты суёшь в него ключ через put(key, value). Он первым делом спрашивает у ключа: "Мужик, какой у тебя хэш-код?" — то есть вызывает hashCode().
  2. По этому хэш-коду он тыкает ключ в определённую корзину (бакет), как письмо в ячейку на почте.
  3. А потом в этой корзине он уже начинает сравнивать ключи через 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 не будет сходить с ума и всё будет работать как часы, а не как мартышлюшка с гранатой.