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

Які наслідки має незмінність String?

Проблема excessive алокації в циклах призводить до:

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

Junior Level

Позитивні наслідки

  1. Рядки безпечні у багатопотоковості — можна передавати між потоками без проблем
  2. Економія пам’яті — однакові рядки зберігаються один раз (String Pool)
  3. Можна використовувати як ключ в HashMap — hashCode не змінюється

Негативні наслідки

  1. Кожна операція створює новий об’єкт — це витрачає пам’ять
  2. Конкатенація в циклі — погана ідея:
    String s = "";
    for (int i = 0; i < 1000; i++) {
     s += i; // Створюється 1000 об'єктів!
    }
    

Рішення — StringBuilder

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i); // Один об'єкт, зміни in-place
}
String s = sb.toString();

Middle Level

Вплив на продуктивність

Проблема excessive алокації в циклах призводить до:

  1. Заповнення Young Generation (Eden space)
  2. Збільшення частоти GC циклів
  3. Фрагментації пам’яті

Правило: для масових змін завжди використовуйте:

  • StringBuilder — однопотоковий, швидкий
  • StringBuffer — багатопотоковий, повільніший

Наслідки для безпеки: робота з паролями

Об’єкт String з паролем буде в пам’яті до рішення GC. Ви не можете програмно “затерти” вміст.

Рекомендація: для чутливих даних використовуйте char[]:

char[] password = console.readPassword();
// Патерн для консольних додатків (System.console().readPassword()).
// Для web-додатків — інші підходи (HTTPS, secure cookie, не зберігати пароль).
// ... використали
Arrays.fill(password, (char) 0); // обнулили

Дизайн коду

Незмінність рядків спонукає до функціонального стилю — методи не змінюють аргументи, а повертають результат. Це робить код передбачуваним та легко тестованим (Pure Functions).


Senior Level

GC Pressure та оптимізації

Незмінність String створює навантаження на GC, але:

  • Нетривалі рядки ефективно очищуються в Minor GC
  • String Deduplication доступна лише при використанні G1 GC (прапорець -XX:+UseStringDeduplication). Для ZGC/Shenandoah — не підтримується.
  • Compact Strings (Java 9+) скорочують споживання пам’яті на 50% для латинських символів

Security-нюанси

TOCTOU-атака (Time-of-Check to Time-of-Use): з незмінними рядками неможлива, оскільки рядок не може змінитися між перевіркою та використанням.

Але є зворотний бік:

  • Рядки з паролями/токенами залишаються в heap dump у відкритому вигляді
  • Рядки можуть бути свопнуті на диск ОС
  • String інтернювання робить рядки довготривалими (Old Gen)

Вплив на JIT-оптимізації

JIT-компілятор може агресивно оптимізувати код з рядками:

  • Constant folding — обчислення константних виразів на етапі компіляції
  • String intrinsics — нативні методи для equals(), hashCode(), indexOf()
  • Escape analysis — усунення непотрібних алокацій при конкатенації

Резюме для Senior

  • Незмінність — «дорого, але надійно»: 1000 конкатенацій = 1000 об’єктів в купі; StringBuilder = 1 об’єкт.
  • Основна ціна — навантаження на GC при необережній роботі в циклах
  • Для секретів використовуйте char[]/byte[], які можна обнулити
  • Для високонавантажених систем важливо розуміти різницю між String та StringBuilder
  • При роботі з секретами пам’ятайте про “залишкове” присутність рядків в купі

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

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

  • Позитивні: потокобезпечність, String Pool, стабільні ключі HashMap
  • Негативні: кожна зміна = новий об’єкт, конкатенація в циклі створює N об’єктів
  • StringBuilder — рішення для масових змін (однопотоковий, швидкий)
  • Паролі в String — погана практика: не можна обнулити, залишається в heap dump
  • Для секретів використовуйте char[] + Arrays.fill(password, (char) 0)
  • String Deduplication — лише G1 GC (-XX:+UseStringDeduplication), не ZGC/Shenandoah
  • JIT оптимізації: constant folding, string intrinsics, escape analysis

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

  • Чому конкатенація в циклі погана? — 1000 ітерацій = 1000 об’єктів в купі → GC pressure
  • Чому паролі не можна зберігати в String? — Не можна програмно затерти, залишається в пам’яті до GC
  • Що таке TOCTOU-атака? — Time-of-Check to Time-of-Use: рядок змінюється між перевіркою та використанням; з незмінним String неможлива
  • Коли використовувати StringBuilder vs String? — StringBuilder для циклів та масових змін, String для зберігання та передачі

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

  • «String += в циклі — нормально» — для 1000 ітерацій це 1000 об’єктів
  • «Паролі можна зберігати в String» — їх неможливо безпечно видалити з пам’яті
  • «char[] завжди безпечніший за String» — char[] теж може бути в heap dump; але його можна обнулити
  • «String Deduplication працює скрізь» — лише G1 GC з прапорцем

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

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