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

Як захистити колекцію від змін?

В Java є кілька способів захистити колекцію:

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

Junior Level

В Java є кілька способів захистити колекцію:

Спосіб 1: Collections.unmodifiableList()

List<String> original = new ArrayList<>();
original.add("A");
List<String> readOnly = Collections.unmodifiableList(original);

readOnly.add("B"); // Викине UnsupportedOperationException!

Але якщо змінити original, зміни будуть видимі через readOnly.

Спосіб 2: List.of() — Java 9+

List<String> immutable = List.of("A", "B", "C");
immutable.add("D"); // UnsupportedOperationException!

Спосіб 3: List.copyOf() — Java 10+

List<String> original = new ArrayList<>();
original.add("A");
List<String> immutable = List.copyOf(original); // створить копію

Коли захист колекції НЕ потрібен

  • Private колекції, які ніколи не покидають клас
  • Read-only довідники, що завантажуються один раз при старті
  • Внутрішні буфери, що використовуються лише для обчислень

Middle Level

Порівняння підходів

Метод Робить копію? Зв’язок з оригіналом Null елементи
Collections.unmodifiableList() Ні Живий зв’язок Дозволені
new ArrayList(original) Так Немає зв’язку Дозволені
List.of() Так Немає зв’язку Заборонені
List.copyOf() Так Немає зв’язку Заборонені

Приклад “живого зв’язку” unmodifiableList:

List<String> original = new ArrayList<>(List.of("A", "B"));
List<String> unmod = Collections.unmodifiableList(original);
original.add("C");
System.out.println(unmod); // [A, B, C] — unmodifiableList бачить зміни original!

List.copyOf може повернути той самий об’єкт, якщо передана колекція вже незмінна. Це оптимізація JDK — зайва копія не створюється.

Коли що використовувати

  • Повернення з гетера: List.copyOf() або Collections.unmodifiableList()
  • В конструкторі: List.copyOf() (створює незалежну копію)
  • Константи: List.of() (оптимізована незмінна колекція)

Захист Map та Set

Map<String, Integer> immutableMap = Map.copyOf(originalMap);
Set<String> immutableSet = Set.copyOf(originalSet);

Важливо: поверхневий захист

Усі ці методи роблять лише Shallow захист — якщо об’єкти в списку змінні, їх стан можна змінити:

List<User> users = List.copyOf(userList);
// users не можна змінити, але:
users.get(0).setName("New Name"); // МОЖНА!

Senior Level

UnmodifiableList під капотом

Collections.unmodifiableList() використовує патерн Decorator. Внутрішній клас UnmodifiableList:

  • Делегує читання оригіналу
  • Блокує запис (UnsupportedOperationException)
  • Ітератор теж обгорнутий — remove() заблоковано

Час створення: O(1), оскільки копія не створюється.

List.copyOf під капотом

List.copyOf() створює нову колекцію типу ImmutableCollections.ListN:

  • Оптимізована за пам’яттю (немає полів для розширення)
  • Якщо вхід вже незмінний — поверне його ж (оптимізація)
  • Час створення: O(n)

Глибокий захист

Для повної незмінності змінних об’єктів в колекції:

  • Використовуйте бібліотеку Vavr (персистентні структури)
  • Або ручне глибоке клонування кожного елемента

Резюме для Senior

  • Для повернення даних — List.copyOf() або Collections.unmodifiableList()
  • Для констант — List.of() (Java 9+)
  • Усі стандартні засоби JDK — лише Shallow Immutability
  • Для гарантованої незмінності всієї структури — Vavr або глибоке клонування

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

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

  • 3 основних способи: Collections.unmodifiableList(), List.of(), List.copyOf()
  • unmodifiableList — обгортка (O(1)), не копія, зв’язок з оригіналом, допускає null
  • List.of() — створює копію, немає зв’язку, null заборонені, для констант
  • List.copyOf() — створює копію, немає зв’язку, null заборонені, оптимізований (може повернути той самий об’єкт)
  • Shallow захист: якщо об’єкти в списку змінні, їх можна змінити через get(0).setName()
  • Для Map/Set: Map.copyOf(), Set.copyOf()

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

  • Що краще — unmodifiableList чи List.copyOf? — unmodifiableList для повернення з гетера (якщо оригінал недоступний), List.copyOf для конструктора
  • List.copyOf копіює якщо вхід вже незмінний? — Ні, поверне той самий об’єкт (оптимізація JDK)
  • Як захистити змінні елементи в списку? — Deep Copy: клонувати кожен елемент
  • List.of vs List.copyOf? — List.of для літералів/констант, List.copyOf для копіювання існуючої колекції

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

  • «UnmodifiableList — повноцінний захист» — це обгортка, оригінал можна змінити
  • «List.copyOf допускає null» — викине NPE
  • «Якщо контейнер незмінний, елементи теж» — ні, змінні елементи можна змінювати
  • unmodifiableList потокобезпечний» — ні, лише незмінність, не thread-safety

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

  • [[10. Що таке захисна копія (defensive copy)]]
  • [[11. Коли потрібно робити захисну копію]]
  • [[13. Що таке Collections.unmodifiableList() і як це працює]]
  • [[14. В чому різниця між shallow copy та deep copy]]
  • [[29. Як правильно працювати з колекціями в незмінних класах]]