TGViewer
Java Ready | Программирование Java Ready | Программирование @java_ready · 9.25K subscribers
Post #2134 1.31K
Почему Collections.unmodifiableList не делает настоящую копию?

В Java часто нужно отдать список наружу так, чтобы вызывающий код не мог его изменить. Например, из getter-метода, DTO, настроек или внутреннего состояния сервиса.

Для этого часто используют Collections.unmodifiableList.
List<String> roles = new ArrayList<>();
List<String> view = Collections.unmodifiableList(roles);


Через view действительно нельзя вызвать add или remove. Такой вызов закончится исключением.

Но важный нюанс в том, что unmodifiableList создаёт не копию, а представление поверх исходного списка.

Если изменить оригинальный список, изменения будут видны и через view.
roles.add("admin");
System.out.println(view);


На экране появится новое значение, хотя сам view вроде бы неизменяемый. Он просто запрещает менять список через себя, но не защищает от изменений оригинала.

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

Если нужна именно независимая копия, лучше использовать List.copyOf.
List<String> snapshot = List.copyOf(roles);


Теперь snapshot не связан с дальнейшими изменениями исходного списка. Это уже снимок состояния на момент создания.

Разница особенно заметна в классах с внутренним состоянием.
class User {
private final List<String> roles = new ArrayList<>();

List<String> roles() {
return List.copyOf(roles);
}
}


Так вызывающий код получает безопасный результат и не может случайно повлиять на объект изнутри.

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

Поэтому для настоящей неизменяемости важно думать не только о коллекции, но и о типах внутри неё. Для строк, чисел, enum и record-объектов такой подход обычно работает хорошо.

Плохой вариант выглядит так.
class User {
private final List<String> roles;

User(List<String> roles) {
this.roles = roles;
}
}


Если внешний код сохранит ссылку на исходный список, он сможет поменять состояние объекта уже после создания.

Безопаснее делать defensive copy прямо в конструкторе.
this.roles = List.copyOf(roles);


Тогда объект получает своё стабильное состояние. Даже если исходный список потом очистят или дополнят, поле внутри User не изменится.

👉 Java Ready | #совет
  • ❤ 9
  • 🔥 7
  • 👍 5
More from @java_ready
  1. Oct 6, 2026Безопасная настройка retry для всей системы! В статье Java-разработчик делится опытом внед…
  2. Oct 5, 2026Почему после Stream.toList() нельзя добавить элемент? При работе со Stream часто нужно соб…
  3. Oct 5, 2026Как оплачивать зарубежные сервисы в 2026 году? Можно бегать между посредниками и бояться б…
  4. Oct 5, 2026Находим три самых популярных товара! Есть поток покупок с повторяющимися названиями. Постр…
  5. Oct 2, 2026photo post
  6. Oct 1, 2026Следим за появлением файлов в папке через WatchService! Создадим наблюдатель и зарегистрир…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →