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

Ответ

Неизменяемость (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.

Ответ 18+ 🔞

Да ты посмотри, что эти умники в Java вытворяют! Вот взяли и сделали класс String намертво замороженным, как труп в вечной мерзлоте. А почему, спрашивается? Да потому что, блядь, если бы строки могли меняться, то это был бы пиздец полный, вот почему!

Представь себе, сука: у тебя пароль в строке лежит. А какой-то мудак из другого потока взял и поменял его на «12345». И пошла пизда по кочкам — безопасность к ебеням, авторизация летит в тартарары. А так — раз создал строку, и она как скала, блядь. Никто её не тронет. Можно хоть в тысячу потоков швырять — всем похуй, она неизменна. Потокобезопасность из коробки, ёпта!

А смотри, какая хитрая жопа придумана с хэш-кодом! Они его один раз посчитали и закэшировали. Потому что строка-то не поменяется, блядь! Зачем каждый раз мозги парить? Вот смотри на этот код, он гениален в своей простоте:

private int hash; // По умолчанию 0
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        hash = h = calculateHash(value);
    }
    return h;
}

Видишь? Посчитали один раз — и в ус не дуем. А если бы строка могла меняться, то её хэш бы тоже менялся, и все твои HashMap превратились бы в тыкву, прости, в ебучую кучу мусора. Ключ изменился — и ищи его, блядь, в этом хэш-угадайке.

А пул строк! Это вообще, блядь, гениальная афера! Благодаря тому, что строки неизменны, можно одну и ту же строку-объект тыщу раз переиспользовать.

String s1 = "Hello"; // Попадает в пул
String s2 = "Hello"; // Переиспользует объект из пула
System.out.println(s1 == s2); // true (один и тот же объект)

Экономия памяти — овердохуищная! И сравнение через == работает, что тоже приятно. Но это всё работает ТОЛЬКО потому, что строка — как отчеканенная монета, её не согнёшь и не перечеканишь.

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

Как они этого добились? Да очень просто, эти хитрожопые архитекторы!

  • Класс объявили final — чтобы никакой умник не наследовался и не накостылял.
  • Массив символов value спрятали в private final — и хрен до него доберёшься.
  • Сеттеров — нихуя. Вообще.
  • Все методы, которые вроде как меняют строку (substring, concat), на самом деле создают новую, блядь. Старую не трогают.

Отсюда и главный вывод, который все знают, но многие игнорируют: не еби мозг со сложением строк в циклах через +. Это пиздец как медленно, потому что каждый раз создаётся новый объект. Для этого есть StringBuilder, мудак! Используй его и не парься.

Вот и вся магия, ёпта. Сделали один раз и на века. Иногда простота — это и есть гениальность, хоть и выглядит как «ну блядь, ну что тут думать-то».