Collections.unmodifiableList: примеры (JAVA)

Collections.unmodifiableList: детали и примеры
Раздел: Коллекции (Collection Framework) - List
Collections.unmodifiableList(List list): List

Общее описание

Метод Collections.unmodifiableList из пакета java.util возвращает «незменяемый вид» (unmodifiable view) на переданный список. При попытке изменить возвращённую коллекцию будет выброшено исключение UnsupportedOperationException. Метод не копирует элементы, поэтому любые изменения в оригинальном списке будут видны через возвращённый объект.

Сигнатура:

public static <T> List<T> unmodifiableList(List<? extends T> list)

Параметры и поведение:

  • list - исходный список. Если передан null, будет выброшено NullPointerException.
  • Метод возвращает объект типа List<T>, представляющий неизменяемое представление исходного списка.
  • Модифицирующие операции на возвращённом списке (например, add, remove, clear) приводят к UnsupportedOperationException.
  • Если исходный список изменяется напрямую (через ссылку на исходный объект), изменения отражаются в «незменяемом виде».
  • Если исходный список реализует RandomAccess, внутренняя реализация может использовать оптимизированный класс, но для пользователя это прозрачно.
  • Сериализуемость: возвращаемый список будет сериализуем, если исходный список сериализуем.

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

Простые примеры использования

1) Базовая обёртка и попытка модификации

import java.util.*;

public class Example1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");

        List<String> unmod = Collections.unmodifiableList(list);
        System.out.println(unmod);
        unmod.add("C"); // приведёт к исключению
    }
}
[A, B]
Exception in thread "main" java.lang.UnsupportedOperationException
    at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1055)
    ...

2) Воздействие на исходный список

import java.util.*;

public class Example2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(Arrays.asList("X", "Y"));
        List<String> view = Collections.unmodifiableList(list);
        System.out.println(view);
        list.add("Z");
        System.out.println(view);
    }
}
[X, Y]
[X, Y, Z]

3) Null-параметр

List<String> v = Collections.unmodifiableList(null);
Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
    at java.base/java.util.Collections.unmodifiableList(Collections.java:??? )
    ...

Похожие возможности в Java

  • List.of(...)
  • Возвращает неизменяемую (immutable) реализацию списка. Создаёт копию и запрещает любые модификации. Предпочтительнее, когда нужна действительно неизменяемая коллекция, безопасная при дальнейшей передаче.

  • List.copyOf(Collection)
  • С Java 10 создаёт неисменяемую копию коллекции. Если переданная коллекция уже неизменяема, может вернуть её напрямую. Полезно для создания снимка (snapshot) без зависимости от оригинала.

  • Collections.synchronizedList
  • Не делает коллекцию неизменяемой, но обеспечивает синхронизацию. Часто комбинируется: Collections.unmodifiableList(Collections.synchronizedList(list)) для возвращения потокобезопасного и незменяемого вида.

  • Guava ImmutableList
  • Библиотечный вариант, создаёт настоящую неизменяемую коллекцию с дополнительными методами. Предпочтителен при работе с Guava и при необходимости расширенных возможностей.

Аналоги в других языках и отличия

  • Python
  • Для неизменяемого списка используется tuple. Кортеж обеспечивает глубокую неизменяемость по структуре, но вложенные изменяемые объекты всё ещё можно менять.

    lst = [1, 2]
    tpl = tuple(lst)
    print(tpl)
    (1, 2)
  • JavaScript
  • Переменная, объявленная через const, не делает сам массив неизменяемым. Для поверхностной заморозки используется Object.freeze, для глубокой - сторонние библиотеки (Immutable.js).

    const arr = [1,2];
    const frozen = Object.freeze(arr);
    console.log(frozen);
    frozen.push(3); // TypeError in strict mode or silent failure
    [1, 2]
    TypeError: Cannot add property 2, object is not extensible
  • C#
  • ReadOnlyCollection<T> или метод List<T>.AsReadOnly() возвращают представление без методов изменения. Есть также System.Collections.Immutable.ImmutableList для настоящей неизменяемости.

    var list = new List<int>{1,2};
    var ro = list.AsReadOnly();
    Console.WriteLine(string.Join(",", ro));
    1,2
    // ro.Add(3) недоступен, метод Add у ReadOnlyCollection отсутствует
  • Kotlin
  • listOf(...) создаёт неизменяемый список. Интерфейс List в Kotlin по умолчанию только для чтения; при необходимости копии используется toList().

    val mutable = mutableListOf(1,2)
    val ro = mutable.toList()
    println(ro)
    [1, 2]
  • Go
  • Нет встроенной неизменяемости для срезов. Часто создаётся копия для защиты от изменений.

    s := []int{1,2}
    copy := append([]int(nil), s...)
    fmt.Println(copy)
    [1 2]
  • PHP
  • Массивы изменяемы, для неизменяемости применяются соглашения или объекты-обёртки. SPL и сторонние библиотеки предоставляют решения.

  • Lua
  • Можно использовать метатаблицы для создания readonly-таблицы. Это требует явной реализации.

Типичные ошибки и ловушки

  • Ожидание полной неизменяемости
  • Collections.unmodifiableList создаёт только вид. Если сохраняется ссылка на исходный список, он может быть изменён извне, и изменения отразятся в "незменяемом" виде.

    List<String> original = new ArrayList<>(List.of("a"));
    List<String> view = Collections.unmodifiableList(original);
    original.set(0, "b");
    System.out.println(view.get(0));
    b
  • Передача null
  • Передача null приводит к NullPointerException.

  • Неправильное использование с subList
  • Если создать вид от list.subList(...), поведение зависит от реализации подсписка: операции над исходным списком могут приводить к ConcurrentModificationException или к отражению изменений в видимой коллекции.

  • Ожидание потокобезопасности
  • unmodifiableList не обеспечивает синхронизацию. Для многопоточного доступа требуется внешняя синхронизация или synchronizedList/immutable коллекции.

Изменения и сопутствующие улучшения

  • Стабильность API
  • Метод присутствует с ранних версий JDK (java.util.Collections) и не претерпел существенных изменений.

  • Новые альтернативы в JDK
  • С Java 9 добавлен List.of, с Java 10 - List.copyOf. Эти методы предлагают более удобные и в ряде случаев безопасные способы получения неизменяемых списков (snapshots либо неизменяемые структуры), поэтому при разработке современных приложений рекомендуется рассматривать их наряду с Collections.unmodifiableList.

Продвинутые и редкие сценарии применения

1) Возврат защищённого вида из API, но с возможностью изменения внутри класса

Пример java
public class Repo {
    private final List<String> internal = new ArrayList<>();

    public Repo() {
        internal.add("one");
    }

    public List<String> getItems() {
        return Collections.unmodifiableList(internal);
    }

    public void addItem(String s) {
        internal.add(s); // изменение видно вызывающему через view
    }
}

// Использование
Repo r = new Repo();
List<String> view = r.getItems();
System.out.println(view);
r.addItem("two");
System.out.println(view);
[one]
[one, two]

Комментарий: полезно при необходимости защищать от прямого мутирования клиентом, но при этом обновлять содержимое через методы класса.

2) Комбинация с synchronizedList для потокобезопасного незменяемого представления

Пример java
List<String> list = Collections.synchronizedList(new ArrayList<>());
list.add("x");
List<String> safeView = Collections.unmodifiableList(list);
// При итерировании требуется синхронизация по list
synchronized(list) {
    for (String s : safeView) {
        System.out.println(s);
    }
}
x

Комментарий: unmodifiableList не обеспечивает синхронизацию; сочетание с synchronizedList создаёт потокобезопасный доступ для чтения через вид.

3) Работа с subList и последствия ConcurrentModification

Пример java
List<Integer> base = new ArrayList<>(List.of(1,2,3,4));
List<Integer> sub = base.subList(1,3); // элементы 2,3
List<Integer> view = Collections.unmodifiableList(sub);
System.out.println(view);
base.add(99); // модификация базового списка меняет структуру
try {
    System.out.println(view.get(0)); // возможен ConcurrentModificationException при итерации
} catch (Exception e) {
    e.printStackTrace();
}
[2, 3]
java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
    ...

Комментарий: subList связывается с оригиналом; изменение оригинала может нарушить итерацию по виду.

4) Сериализация возвращённого вида при сериализуемом исходнике

Пример java
// Если оригинал Serializable, то и unmodifiableList будет Serializable
ArrayList<String> orig = new ArrayList<>(List.of("a","b"));
List<String> view = Collections.unmodifiableList(orig);
// view можно сериализовать стандартными средствами
(нет вывода, но сериализация возможна если orig сериализуем)

5) Сравнение с List.copyOf для получения snapshot

Пример java
List<String> src = new ArrayList<>(List.of("a"));
List<String> view = Collections.unmodifiableList(src);
List<String> copy = List.copyOf(src);
src.add("b");
System.out.println(view); // отразит b
System.out.println(copy); // останется без b
[a, b]
[a]

Комментарий: List.copyOf подходит для создания неизменяемого снимка; unmodifiableList полезен при желании получить вид без копирования.

джава Collections.unmodifiableList function comments

En
Collections.unmodifiableList Returns an unmodifiable view of the specified list