← Назад к вопросам

Что нельзя модифицировать в HashMap

2.0 Middle🔥 111 комментариев
#Коллекции

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что нельзя менять в HashMap - Immutability правила

В HashMap есть строгие правила о том, что можно и нельзя изменять, особенно если это влияет на хеширование и поиск элементов.

Главное правило: ключи должны быть immutable (неизменяемые)

❌ НЕЛЬЗЯ изменять:

  1. Сами ключи (их состояние после добавления в HashMap)
  2. hashCode() ключей (вычисляется при добавлении и используется для поиска)
  3. equals() ключей (используется для проверки совпадения в цепочке)

Почему это важно?

public class KeyMutabilityExample {
    
    static class Person {
        String name;
        int age;
        
        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public int hashCode() {
            return name.hashCode();  // Хеш зависит от name
        }
        
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof Person)) return false;
            return name.equals(((Person) obj).name);
        }
    }
    
    public static void main(String[] args) {
        HashMap<Person, String> map = new HashMap<>();
        
        Person person = new Person("Alice", 25);
        map.put(person, "Engineer");  // Добавляем
        
        System.out.println(map.get(person));  // "Engineer" ✓
        
        // ❌ ОШИБКА: изменяем ключ после добавления
        person.name = "Bob";  // Меняем имя!
        
        // Теперь hashCode вернёт другое значение
        System.out.println(map.get(person));  // null ✗
        
        // Элемент всё ещё в HashMap, но мы его не можем найти!
        System.out.println(map.size());  // 1 (элемент есть!)
        System.out.println(map.containsKey(person));  // false ✗
        
        // Порча структуры HashMap
        for (Person p : map.keySet()) {
            System.out.println(p.name);  // "Alice", но мы изменили на "Bob"!
        }
    }
}

Как HashMap находит элементы

Поиск в HashMap:

1. HashMap.get(key):
   ├─ Вычислить hash = key.hashCode()
   ├─ Найти бакет по индексу: index = hash & (capacity - 1)
   ├─ Итерировать цепочку в бакете
   │  └─ Для каждого Node:
   │     ├─ Если node.hash != hash → пропустить
   │     └─ Если key.equals(node.key) → найдено!
   └─ Если не найдено → return null

ПРОБЛЕМА: если key.hashCode() изменился, поиск пойдёт
          в ДРУГОЙ бакет и элемент не будет найден!

Конкретный пример с изменением hashCode

public class HashCodeMutation {
    
    static class MutableKey {
        int value;
        
        MutableKey(int value) {
            this.value = value;
        }
        
        @Override
        public int hashCode() {
            return value;  // Хеш зависит от value
        }
        
        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof MutableKey)) return false;
            return value == ((MutableKey) obj).value;
        }
    }
    
    public static void main(String[] args) {
        HashMap<MutableKey, String> map = new HashMap<>();
        
        MutableKey key = new MutableKey(10);
        map.put(key, "Initial");
        
        // Внутри HashMap:
        // hash = 10
        // index = 10 & (capacity - 1) = 10
        // buckets[10] → Node(key, "Initial")
        
        System.out.println(map.get(key));  // "Initial" ✓
        
        // ❌ Изменяем ключ!
        key.value = 20;
        
        // Теперь:
        // hash = 20
        // index = 20 & (capacity - 1) = 4
        // Ищет в buckets[4], но элемент в buckets[10]!
        
        System.out.println(map.get(key));  // null ✗ (ОШИБКА!)
        System.out.println(map.containsKey(key));  // false ✗
        System.out.println(map.size());  // 1 (элемент всё ещё там!)
    }
}

Что можно изменять?

✓ МОЖНО менять:

HashMap<String, Person> map = new HashMap<>();

Person person = new Person("Alice", 25);
map.put("alice-key", person);

// ✓ Можно менять сами значения
person.age = 26;  // OK - это значение, а не ключ
map.put("alice-key", new Person("Bob", 30));  // OK

// ✓ Можно добавлять новые ключ-значение пары
map.put("bob-key", new Person("Bob", 30));  // OK

// ✓ Можно удалять элементы
map.remove("alice-key");  // OK

❌ НЕЛЬЗЯ менять:

HashMap<MutableKey, String> map = new HashMap<>();

MutableKey key = new MutableKey(10);
map.put(key, "Value");

// ❌ Менять состояние ключа
key.value = 20;  // НЕПРАВИЛЬНО! Портит HashMap

// ❌ Менять ключ через entrySet
for (Map.Entry<MutableKey, String> entry : map.entrySet()) {
    entry.getKey().value = 999;  // ❌ ОПАСНО!
}

Правильная практика: immutable ключи

// ✓ Хорошо: используем immutable классы как ключи

// 1. String (immutable по умолчанию)
HashMap<String, Integer> map = new HashMap<>();
map.put("Alice", 25);  // ✓ Безопасно

// 2. Integer, Long, etc (immutable примитивные обёртки)
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "One");  // ✓ Безопасно

// 3. Самодельный immutable класс
public class ImmutableKey {
    private final String name;  // final - неизменяемо
    private final int id;        // final - неизменяемо
    
    public ImmutableKey(String name, int id) {
        this.name = name;
        this.id = id;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, id);  // На основе final полей
    }
    
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof ImmutableKey)) return false;
        ImmutableKey other = (ImmutableKey) obj;
        return name.equals(other.name) && id == other.id;
    }
}

HashMap<ImmutableKey, String> map = new HashMap<>();
ImmutableKey key = new ImmutableKey("Alice", 1);
map.put(key, "Engineer");  // ✓ Безопасно

Диагностика проблемы в коде

// Если видишь такой код — это красный флаг:

HashMap<List<String>, Integer> map = new HashMap<>();
List<String> list = Arrays.asList("a", "b");
map.put(list, 100);

list.add("c");  // ❌ Список изменён! Портит HashMap

System.out.println(map.get(list));  // null или ошибка

Правило безопасности

ПРАВИЛО: Если класс используется как ключ в HashMap,
         то его hashCode() и equals() должны быть основаны
         ТОЛЬКО на final (неизменяемых) полях.

Проверка:

@Override
public int hashCode() {
    return Objects.hash(
        this.finalField1,   // ✓ OK
        this.finalField2    // ✓ OK
        // this.mutableField // ❌ НИКОГДА!
    );
}

Альтернативы

Если нужны мутируемые объекты, использовать:

// 1. TreeMap с Comparator (не зависит от hashCode)
TreeMap<MutableKey, String> map = new TreeMap<>();

// 2. Создавать новый ключ при изменении
HashMap<String, Person> map = new HashMap<>();
Person person = new Person("Alice", 25);
map.put(createKey(person), person);

// При изменении:
map.remove(createKey(person));  // Удалить старую запись
person.age = 26;
map.put(createKey(person), person);  // Добавить новую

Главное правило: ключи в HashMap должны быть immutable и не менять свой hashCode() после добавления.

Что нельзя модифицировать в HashMap | PrepBro