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

Що таке Collections.unmodifiableList() і як це працює?

Collections.unmodifiableList() — це метод, який створює обгортку "лише для читання" — об'єкт-посередник, який перенаправляє усі виклики до оригінального списку, але блокує метод...

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

Junior Level

Collections.unmodifiableList() — це метод, який створює обгортку “лише для читання” — об’єкт-посередник, який перенаправляє усі виклики до оригінального списку, але блокує методи модифікації.

List<String> list = new ArrayList<>();
list.add("A");

List<String> readOnly = Collections.unmodifiableList(list);
System.out.println(readOnly.get(0)); // "A" — можна читати
readOnly.add("B"); // ПОМИЛКА! UnsupportedOperationException

Важливо: це обгортка, а не копія. Якщо змінити оригінальний список, зміни будуть видимі через обгортку.


Middle Level

Як це працює

Метод повертає об’єкт внутрішнього класу Collections.UnmodifiableList, який:

  • Читання (get(), size(), contains()) — делегує оригінальному списку
  • Запис (add(), remove(), clear()) — викидає UnsupportedOperationException
  • Ітератор — теж обгорнутий, remove() заблоковано

Головна пастка

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

List<String> readOnly = Collections.unmodifiableList(original);
original.add("B"); // Змінюємо оригінал

System.out.println(readOnly); // [A, B] — зміни видимі!

UnmodifiableList vs List.copyOf()

Характеристика unmodifiableList List.copyOf
Копіювання Ні Так
Зв’язок з оригіналом Зберігається Розривається
Допускає null Так Ні (NPE)
Швидкість створення O(1) O(n)
Потокобезпечність Ні Ні

// synchronizedList забезпечує потокобезпечність, але НЕ незмінність. // unmodifiableList забезпечує незмінність, але НЕ потокобезпечність.

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

  • В гетеріunmodifiableList для економії пам’яті (якщо клієнт не добереться до оригіналу)
  • В конструкторіList.copyOf() для справжнього захисту

Senior Level

Декоратор патерн під капотом

UnmodifiableList — класичний приклад Decorator Pattern:

// Спрощено
class UnmodifiableList<E> implements List<E> {
    private final List<E> list; // посилання на оригінал

    public E get(int index) { return list.get(index); } // делегування
    public boolean add(E e) { throw new UnsupportedOperationException(); }
}

Продуктивність

  • Створення: O(1) — жодних копій
  • Читання: O(1) — прямий виклик до оригіналу
  • Пам’ять: мінімальна — лише обгортка

Thread-safety нюанс

unmodifiableList не робить список потокобезпечним. Якщо оригінальний список змінюється іншим потоком, ви можете побачити ConcurrentModificationException або неконсистентний стан. Для потокобезпечності потрібна CopyOnWriteArrayList або Collections.synchronizedList().

Попередження: доступ до оригіналу

Якщо код, що викликає, може зберегти посилання на оригінальний список, unmodifiableList не забезпечить захист. caller може змінити оригінал напряму!

Резюме для Senior

  • unmodifiableList — це View, а не копія
  • Ефективний спосіб (O(1)) обмежити права доступу до колекції
  • Не захищає від змін оригінальної колекції
  • Для справжньої незмінності переважніше List.of() та List.copyOf()

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

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

  • unmodifiableList — обгортка (Decorator Pattern), не копія: O(1) створення, зв’язок з оригіналом
  • Читання делегує оригіналу, запис викидає UnsupportedOperationException
  • Ітератор теж обгорнутий — remove() заблоковано
  • Головна пастка: зміна оригіналу видима через обгортку
  • unmodifiableList НЕ робить список потокобезпечним — потрібна CopyOnWriteArrayList або synchronizedList
  • Якщо caller збереже посилання на оригінал — unmodifiableList не забезпечить захист

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

  • UnmodifiableList vs List.copyOf? — UnmodifiableList — обгортка (O(1), зв’язок), List.copyOf — копія (O(n), немає зв’язку)
  • Чи можна додати елемент через обгортку? — Ні, UnsupportedOperationException
  • UnmodifiableList потокобезпечний? — Ні, лише блокує запис; якщо оригінал змінюється іншим потоком — ConcurrentModificationException
  • Коли використовувати unmodifiableList? — В гетері, якщо клієнт не добереться до оригіналу

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

  • «UnmodifiableList — це копія» — це обгортка, оригінал видимий
  • «UnmodifiableList = thread-safe» — ні, потрібна synchronizedList або CopyOnWriteArrayList
  • synchronizedList забезпечує незмінність» — ні, лише потокобезпечність
  • «Ітератор unmodifiableList дозволяє remove()» — ітератор теж обгорнутий, remove() заблоковано

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

  • [[10. Що таке захисна копія (defensive copy)]]
  • [[11. Коли потрібно робити захисну копію]]
  • [[12. Як захистити колекцію від змін]]
  • [[29. Як правильно працювати з колекціями в незмінних класах]]