Из-за какого контракта список, созданный через Arrays.asList, принимает замену элемента, но отклоняет добавление и удаление?
Arrays.asList возвращает список фиксированного размера, связанный с исходным массивом. Поэтому операция set разрешена: она заменяет существующий элемент без изменения размера. Операции add и remove являются структурными и завершаются UnsupportedOperationException.
Подход Arrays.asList появился как адаптер между массивами и Java Collections Framework. Он позволяет передать массив туда, где ожидается List, не создавая отдельную копию и не реализуя обёртку вручную.
Отсутствие изменения размера сохраняет простую связь списка с массивом. За это приходится платить ограниченной поддержкой операций: список предоставляет только те изменения, которые можно выполнить в пределах существующей длины массива.
Разработчик может воспринимать результат Arrays.asList как обычный изменяемый список и передать его в код, который добавляет или удаляет элементы. В таком случае программа завершится исключением во время выполнения, хотя замена существующего элемента будет работать.
Ошибка особенно вероятна при подготовке списка для дальнейшего накопления элементов. Если требуется изменять размер коллекции, нужно явно создать независимый изменяемый список.
Контракт List допускает так называемые необязательные операции. Конкретная реализация может поддерживать set, но не поддерживать add, remove и другие операции, меняющие размер.
Arrays.asList использует исходный массив как хранилище. Индексная замена обновляет ячейку этого массива, поэтому set поддерживается. Добавление потребовало бы увеличить вместимость и изменить размер представления, а удаление — сдвинуть элементы и уменьшить размер; для фиксированного массива это не соответствует устройству реализации.
Если нужен обычный список с изменяемым размером, используют копирование, например new ArrayList<>(Arrays.asList(...)). Это требует дополнительной памяти и отделяет список от исходного массива, зато позволяет безопасно выполнять структурные изменения.
Важно также, что изменения массива отражаются в списке, а set списка отражается в массиве. Это представление, а не независимая копия. Для полностью неизменяемого списка применяются другие средства, например List.of, но у него не поддерживается даже set.
Сервис получает массив разрешённых ролей и преобразует его в список. Затем другой компонент добавляет временную роль перед выполнением операции. Если передать ему результат Arrays.asList, добавление завершится UnsupportedOperationException.
Рассматривались два варианта. Оставить Arrays.asList — экономнее и сохраняет связь с массивом, но не подходит для изменения размера. Использовать List.of — обеспечивает неизменяемость, однако запрещает любые изменения. Выбран ArrayList на основе исходного массива: это требует копирования, но даёт независимую коллекцию с поддержкой add и remove, что соответствует требованиям компонента.
set у списка?Да. Список, возвращённый Arrays.asList, является представлением исходного массива. Замена элемента через set записывается в массив, а прямое изменение массива будет видно при чтении списка. Это отличается от ArrayList, созданного через копирующий конструктор.
add нарушением контракта List?Потому что add относится к необязательным операциям интерфейса List. Реализация обязана корректно выполнить операцию, если она её поддерживает, но может сигнализировать о неподдерживаемой операции через UnsupportedOperationException. Поэтому тип List сам по себе не гарантирует изменение размера.
Arrays.asList от List.of в отношении изменений?Arrays.asList допускает set, поскольку его размер фиксирован, но элементы можно заменить. List.of не поддерживает изменения вообще: вызовы set, add и remove запрещены. Кроме того, Arrays.asList допускает null, тогда как фабричные списки List.of не принимают null-элементы.