← Назад к вопросам
Какие методы ключа вызываются при вставке в HashMap?
2.3 Middle🔥 81 комментариев
#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Методы ключа, вызываемые при вставке в HashMap
Это один из самых важных вопросов для понимания HashMap. При вставке элемента в HashMap вызываются критические методы ключа, от которых зависит корректная работа.
Методы, вызываемые при вставке
1. hashCode()
hashCode() — это первый и самый важный метод. HashMap использует его для определения индекса bucket'а, где будет храниться элемент.
public class CustomKey {
private String id;
@Override
public int hashCode() {
// Вызывается при каждой вставке/получении из HashMap
System.out.println("hashCode() вызван");
return id.hashCode();
}
}
public class Main {
public static void main(String[] args) {
Map<CustomKey, String> map = new HashMap<>();
CustomKey key = new CustomKey("key1");
map.put(key, "value1");
// Вывод: hashCode() вызван
map.put(key, "value2");
// Вывод: hashCode() вызван (ещё раз)
}
}
Процесс:
1. HashMap вычисляет индекс: index = hash(key.hashCode()) & (capacity - 1)
2. Переходит к bucket'у с этим индексом
3. Если bucket пуст — вставляет элемент
4. Если bucket не пуст — проверяет существующие ключи (вызывает equals())
2. equals()
equals() — вызывается только если hashCode() вернул тот же значение, что и существующий ключ. Это проверка на точное совпадение.
public class CustomKey {
private String id;
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
// Вызывается только если hashCode совпадает
System.out.println("equals() вызван");
if (this == obj) return true;
if (!(obj instanceof CustomKey)) return false;
CustomKey other = (CustomKey) obj;
return this.id.equals(other.id);
}
}
public class Main {
public static void main(String[] args) {
Map<CustomKey, String> map = new HashMap<>();
CustomKey key1 = new CustomKey("key1");
CustomKey key2 = new CustomKey("key1"); // Другой объект, но equals = true
map.put(key1, "value1");
System.out.println("--- Вставляем ещё раз ---");
map.put(key2, "value2"); // Перезапишет значение
// Вывод:
// hashCode() вызван
// --- Вставляем ещё раз ---
// hashCode() вызван
// equals() вызван
System.out.println(map.size()); // 1 (не 2!)
}
}
Сценарии вызова методов
Сценарий 1: Новый ключ (разный hashCode)
Map<CustomKey, String> map = new HashMap<>();
CustomKey key1 = new CustomKey("key1");
CustomKey key2 = new CustomKey("key2"); // Другой hashCode
map.put(key1, "value1");
map.put(key2, "value2");
// Вывод (в порядке):
// hashCode() -> key1.hashCode() вычислен
// hashCode() -> key2.hashCode() вычислен
// equals() НЕ вызывается! (разные bucket'ы)
System.out.println(map.size()); // 2
Сценарий 2: Коллизия hashCode (одинаковый hashCode, разные объекты)
class BadKey {
private String value;
@Override
public int hashCode() {
return 42; // Плохая реализация: всегда возвращает одно значение
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof BadKey)) return false;
return this.value.equals(((BadKey) obj).value);
}
}
Map<BadKey, String> map = new HashMap<>();
BadKey key1 = new BadKey("a");
BadKey key2 = new BadKey("b");
map.put(key1, "value1");
map.put(key2, "value2");
// Вывод:
// hashCode() -> 42
// hashCode() -> 42 (одинаковые!)
// equals() -> false (разные значения)
// Оба элемента добавлены в одном bucket'е
System.out.println(map.size()); // 2 (но они в одном bucket'е)
Сценарий 3: Перезапись (одинаковый hashCode и equals)
Map<CustomKey, String> map = new HashMap<>();
CustomKey key1 = new CustomKey("key1");
CustomKey key2 = new CustomKey("key1"); // equals() == true
map.put(key1, "value1");
map.put(key2, "value2");
// Вывод:
// hashCode() -> вычислен для key1
// hashCode() -> вычислен для key2 (одинаковые!)
// equals() -> true (это один и тот же ключ)
// Значение перезаписано!
System.out.println(map.size()); // 1
System.out.println(map.get(key1)); // "value2"
Правила реализации hashCode() и equals()
public class ProperKey {
private String id;
private int priority;
@Override
public boolean equals(Object obj) {
// 1. Проверка идентичности
if (this == obj) return true;
// 2. Проверка null
if (obj == null) return false;
// 3. Проверка типа
if (getClass() != obj.getClass()) return false;
// 4. Сравнение всех полей, используемых в hashCode()
ProperKey other = (ProperKey) obj;
return Objects.equals(this.id, other.id) &&
this.priority == other.priority;
}
@Override
public int hashCode() {
// Используй Objects.hash() для правильного хеширования
// Включай ВСЕ поля, которые используются в equals()
return Objects.hash(id, priority);
}
}
Последствия неправильной реализации
Проблема 1: Изменение ключа после вставки
public class MutableKey {
private String value;
public void setValue(String value) {
this.value = value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof MutableKey)) return false;
return this.value.equals(((MutableKey) obj).value);
}
}
Map<MutableKey, String> map = new HashMap<>();
MutableKey key = new MutableKey("key1");
map.put(key, "value1");
key.setValue("key2"); // ОШИБКА: изменили ключ!
// Теперь ключ находится в неправильном bucket'е
System.out.println(map.get(key)); // null (не найдёт!)
System.out.println(map.containsKey(key)); // false
Проблема 2: Несоответствие между hashCode() и equals()
public class BadImplementation {
private String id;
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
// ОШИБКА: equals() проверяет больше, чем hashCode()
return this == obj; // Только идентичность
}
}
// Такой код нарушает contract HashMap
// Если hashCode() равны, но equals() false — проблемы!
Практический пример с отладкой
public class DebugExample {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
String key1 = new String("test");
String key2 = new String("test");
map.put(key1, "value1");
System.out.println("key1.hashCode() = " + key1.hashCode());
System.out.println("key2.hashCode() = " + key2.hashCode());
System.out.println("key1.equals(key2) = " + key1.equals(key2));
map.put(key2, "value2");
System.out.println("map.size() = " + map.size()); // 1
System.out.println("map.get(key1) = " + map.get(key1)); // value2
// Вывод:
// key1.hashCode() = 3556498
// key2.hashCode() = 3556498
// key1.equals(key2) = true
// map.size() = 1
// map.get(key1) = value2
}
}
Выводы
При вставке в HashMap вызываются:
- hashCode() — ВСЕГДА (для определения bucket'а)
- equals() — ТОЛЬКО если существует ключ с одинаковым hashCode
Правила:
- Если equals() возвращает true, то hashCode() должен быть одинаковый
- Если hashCode() одинаковый, equals() может быть true или false
- Никогда не изменяй ключ после вставки в HashMap
- Используй immutable объекты как ключи
- Правильно реализуй оба метода вместе