Что отличает представление Collections.unmodifiableList от независимой неизменяемой копии списка?

Что отличает представление Collections.unmodifiableList от независимой неизменяемой копии списка?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Collections.unmodifiableList создаёт не копию, а read-only-представление исходного списка. Изменять список через это представление нельзя, но изменения исходной коллекции будут видны в представлении. Для независимого снимка нужны копирование или фабрика неизменяемой коллекции, например List.copyOf.

Исторический контекст

Java Collections Framework предоставляет общие интерфейсы и служебные обёртки для работы с коллекциями без дублирования данных. Представление без права записи решает задачу передачи коллекции потребителю, которому разрешено чтение, но запрещена модификация.

Такой подход дешевле копирования и позволяет наблюдать актуальное содержимое исходной коллекции. Однако он намеренно не делает данные неизменяемыми: защита действует только через конкретную обёртку.

Постановка проблемы

Если передать наружу исходный изменяемый список, вызывающий код сможет изменить внутреннее состояние объекта. Если передать unmodifiableList, прямые операции записи через полученную ссылку завершатся UnsupportedOperationException, но владелец исходного списка по-прежнему может его менять.

Неверное ожидание независимого снимка приводит к неожиданным изменениям данных у читателя. Дополнительно обёртка не добавляет потокобезопасность: одновременное чтение и изменение исходного списка требуют отдельной синхронизации или другой стратегии публикации.

Подробное решение

Collections.unmodifiableList хранит ссылку на исходный список и делегирует ему операции чтения. Операции изменения через обёртку не делегируются и отклоняются исключением, поэтому это защита интерфейса доступа, а не копирование состояния.

import java.util.ArrayList; import java.util.Collections; import java.util.List; List<String> source = new ArrayList<>(); source.add("A"); List<String> view = Collections.unmodifiableList(source); source.add("B"); System.out.println(view); // [A, B] view.add("C"); // UnsupportedOperationException

После добавления в source элемент появляется в view, потому что обе ссылки связаны с одним объектом. Вызов add через представление запрещён, но это не препятствует изменению исходного списка другим владельцем.

Если требуется независимый неизменяемый снимок, подходит List.copyOf(source): результат не связан с последующими изменениями исходного списка и не поддерживает модификацию. У этого варианта есть ограничение: List.copyOf не принимает null-элементы; при необходимости сохранить их можно сначала создать копию, например через new ArrayList<>(source), а затем обернуть её в Collections.unmodifiableList.

Для многопоточного кода нельзя считать одну из этих конструкций автоматически безопасной. Часто применяют публикацию полностью подготовленного неизменяемого снимка, синхронизацию доступа или потокобезопасную коллекцию — выбор зависит от того, нужен ли живой список и как часто происходят записи.

Ситуация из практики

Сервис хранит список разрешённых регионов и передаёт его нескольким компонентам. Требование — компоненты не должны менять список, но после обновления конфигурации они должны сразу видеть новые значения.

Вариант с List.copyOf обеспечивает стабильный снимок и безопаснее для чтения, но уже переданные компоненты не увидят обновление. Простое копирование при каждом чтении устраняет связанность, однако увеличивает затраты по времени и памяти.

Выбранное решение — публиковать unmodifiableList поверх внутреннего списка только при наличии внешней синхронизации. Если обновления должны быть атомарными и читатели не должны наблюдать промежуточное состояние, лучше создавать новый неизменяемый снимок и атомарно заменять ссылку на него.

Таким образом, представление выбирают для живого read-only-доступа, а снимок — для независимого и стабильного состояния. Ключевой критерий — нужна ли видимость последующих изменений исходной коллекции.

Что кандидаты часто упускают

  1. Защищает ли unmodifiableList сам исходный список?

    Нет. Защита действует только на операции, выполненные через ссылку на обёртку. Любой код, сохранивший ссылку на исходный список, может изменить его, и эти изменения станут видны через представление.

  2. Гарантирует ли unmodifiableList потокобезопасность?

    Нет. Обёртка не добавляет синхронизацию и не устраняет гонки между чтением и изменением исходной коллекции. При конкурентном доступе нужно отдельно обеспечить безопасную публикацию и синхронизацию либо использовать подход с заменой готового неизменяемого снимка.

  3. Всегда ли List.copyOf является полной независимой копией элементов?

    Нет. Он создаёт независимую структуру списка, но не клонирует сами элементы. Если элементы изменяемы, их внутреннее состояние может измениться через другие ссылки, даже хотя состав и порядок элементов полученного списка остаются неизменными.