Почему неизменяемость (immutability) объектов String важна при их использовании в качестве ключа в HashMap или HashSet?

«Почему неизменяемость (immutability) объектов String важна при их использовании в качестве ключа в HashMap или HashSet?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Неизменяемость String является фундаментальным свойством, которое обеспечивает корректную и безопасную работу при использовании строк в качестве ключей в хэш-коллекциях (HashMap, HashSet, ConcurrentHashMap).

Ключевые причины:

  1. Гарантия постоянства хэш-кода (hashCode()). Позиция объекта в хэш-таблице определяется его хэш-кодом на момент помещения. Если после добавления ключа изменить его состояние (что невозможно для String), хэш-код изменится. Последующий поиск по get(key) будет выполняться по новому хэш-коду в другой "корзине" (bucket), и элемент не будет найден, хотя физически он останется в коллекции (утечка памяти и логическая ошибка).

  2. Сохранение контракта между equals() и hashCode(). Контракт требует, чтобы равные объекты имели равные хэш-коды. Изменение ключа может нарушить это правило, сделав коллекцию неконсистентной.

  3. Потокобезопасность. Неизменяемые объекты по своей природе потокобезопасны. Ключ может быть безопасно использован из нескольких потоков без риска состояния гонки (race condition) и без необходимости в синхронизации.

Наглядный пример проблемы с изменяемым ключом:

// Демонстрация гипотетической проблемы, если бы String был изменяемым
public class MutableKeyDemo {
    public static void main(String[] args) {
        Map<String, Integer> budget = new HashMap<>();
        String key = "java";
        budget.put(key, 100); // Хэш-код вычисляется от "java"

        // Гипотетический метод, изменяющий внутреннее состояние строки
        // key.mutateTo("JAVA"); // Если бы это было возможно...

        // Поиск по исходному ключу "java" теперь может не сработать,
        // так как его хэш-код изменился.
        Integer value = budget.get(key); // Может вернуть null
        System.out.println("Бюджет на " + key + ": " + value);
    }
}

Вывод: Использование неизменяемых типов для ключей — это best practice, предотвращающее тонкие и сложные в отладке ошибки в логике работы коллекций.