Почему класс String в Java является неизменяемым (immutable)?

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

Ответ

Неизменяемость (immutability) класса String — это фундаментальный дизайн-выбор, который обеспечивает безопасность, производительность и предсказуемость.

Ключевые причины и преимущества:

  1. Безопасность и потокобезопасность:

    • Строки часто хранят конфиденциальные данные (пароли, токены). Неизменяемость гарантирует, что они не будут изменены после создания.
    • Объекты String можно безопасно использовать в многопоточных средах без синхронизации.
  2. Кэширование хэш-кода:

    // Поле hash кэшируется при первом вычислении
    private int hash; // По умолчанию 0
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            hash = h = calculateHash(value);
        }
        return h;
    }
    • Хэш-код вычисляется один раз, что критически важно для производительности в HashMap, HashSet.
  3. Пулы строк (String Pool):

    String s1 = "Hello"; // Попадает в пул
    String s2 = "Hello"; // Переиспользует объект из пула
    System.out.println(s1 == s2); // true (один и тот же объект)
    • Пул возможен только благодаря неизменяемости, что экономит память и ускоряет сравнение через ==.
  4. Безопасная передача в качестве ключа:

    Map<String, Integer> cache = new HashMap<>();
    String key = "config";
    cache.put(key, 42);
    // Гарантия: ключ не может измениться и не "сломает" хэш-таблицу
  5. Защита от случайного изменения: Библиотечные методы, получающие String, могут быть уверены, что исходные данные не изменятся.

Как достигается неизменяемость?

  • Класс объявлен как final (нельзя наследоваться).
  • Поле value (массив символов) — private final.
  • Отсутствуют сеттеры для изменения value.
  • Методы, якобы изменяющие строку (например, substring, concat), возвращают новый объект String.

Следствие: Операции конкатенации в циклах неэффективны — для этого следует использовать StringBuilder.