Питання 4 · Розділ 13

Чому клас String є незмінним?

Клас String в Java не можна змінити після створення. Будь-яка операція, яка "змінює" рядок, насправді створює новий рядок.

Мовні версії: English Russian Ukrainian

Junior Level

Клас String в Java не можна змінити після створення. Будь-яка операція, яка “змінює” рядок, насправді створює новий рядок.

String s = "Hello";
s.concat(" World"); // s все ще "Hello", результат потрібно присвоїти
s = s.concat(" World"); // тепер s = "Hello World" (новий об'єкт)

Чому так зробили?

  1. Безпека — рядки використовуються для шляхів до файлів, URL, підключень до БД
  2. Потокобезпечність — рядки можна передавати між потоками без проблем
  3. Економія пам’яті — однакові рядки повторно використовуються (String Pool)

Зворотний бік: зберігання паролів у String — погана практика (див. файл 5).


Middle Level

String Pool

При створенні рядка-літерала JVM перевіряє, чи вже є такий рядок у пулі. Якщо є — повертає посилання на нього, якщо ні — створює новий:

String a = "Hello";
String b = "Hello";
System.out.println(a == b); // true — одне й те саме посилання з пулу

Безпека

Рядки використовуються як параметри при відкритті файлів, мережевих з’єднань, підключенні до БД. Якби рядки були змінними, зловмисник міг би передати валідний шлях, дочекатися перевірки, а потім змінити його в пам’яті.

Стабільність hashCode

String кешує свій hashCode() при першому обчисленні. Це робить рядки ідеальними ключами для HashMap:

Map<String, Integer> map = new HashMap<>();
map.put("key", 42); // hashCode обчислено один раз
map.get("key");     // використовується той самий hashCode

Як це реалізовано в JDK

  • Клас позначено як final (заборона успадкування)
  • Внутрішнє поле byte[] value (Java 9+) або char[] (старі версії) — private final
  • Немає методів, що модифікують це поле

Senior Level

Архітектурне значення

Незмінність String — це не просто зручність, а фундаментальний architectural decision, на якому базуються:

  1. String Pool (патерн Flyweight) — без незмінності пул неможливий: зміна рядка в одному місці зламає всі посилання на нього
  2. Security Manager — перевірки доступу до файлів, мереж, класів базуються на незмінності рядків-параметрів
  3. Class Loading — імена класів та шляхів завантаження незмінні, що запобігає підміні класів
  4. Стабільність HashMap — кешований hashCode гарантує коректну роботу хеш-таблиць

Еволюція реалізації

  • Java 8 і раніше: private final char[] value — кожен символ 2 байти (UTF-16)
  • Java 9+: private final byte[] value + поле coder — Compact Strings (Latin-1 = 1 байт, UTF-16 = 2 байти) // Заміна char[] на byte[] (Java 9+, Compact Strings) економить ~50% пам’яті для рядків з латинськими символами.

Резюме для Senior

  • Незмінність String — фундамент для String Pool, Security та Thread Safety
  • Дозволяє JVM оптимізувати роботу з пам’яттю та кешувати хеш-коди
  • Будь-яка “зміна” рядка породжує новий об’єкт в купі
  • Без незмінності String вся платформа Java була б вразливою. Приклад TOCTOU-атаки: перевіряється шлях /safe/path, але потім рядок змінюється на /etc/passwd.

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • String — final клас, приватне поле byte[] value, немає методів модифікації
  • String Pool — однакові рядки повторно використовуються, працює ЛИШЕ завдяки незмінності
  • Безпека — рядки використовуються для шляхів до файлів, URL, підключень до БД
  • Стабільність hashCode — String кешує hashCode, ідеальний як ключ HashMap
  • Еволюція: Java 8 char[] (2 байти/символ) → Java 9+ byte[] + coder (Compact Strings, 1 байт для Latin-1)
  • Без незмінності String вся платформа Java вразлива (TOCTOU-атаки)

Часті уточнюючі запитання:

  • Чому String final? — Щоб підклас не додав змінність і не перевизначив hashCode/equals
  • Що таке Compact Strings? — Java 9+: латинські символи зберігаються в 1 байті замість 2
  • Чому String кешує hashCode? — Обчислюється один раз, прискорює роботу в HashMap
  • Що було б без незмінності String? — String Pool неможливий, Security Manager обойдено

Червоні прапорці (НЕ говорити):

  • «String можна змінювати через concat» — concat створює НОВИЙ рядок, оригінал не змінюється
  • «String Pool в PermGen» — з Java 7+ пул переїхав у Heap
  • «Незмінність String — лише зручність» — це фундаментальний architectural decision
  • «byte[] означає String mutable» — поле private final, доступу ззовні немає

Пов’язані теми:

  • [[5. Які наслідки незмінності String]]
  • [[18. Як працює String pool і як це пов’язано з ім’ютабельністю]]
  • [[19. Чи можна змінити значення String через рефлексію]]
  • [[27. Чи можна використовувати незмінні об’єкти як ключі в HashMap]]