Почему возникнет exception при попытке изменить unmodifiable collection?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Исключение при изменении Unmodifiable Collection
Это хороший вопрос, который показывает глубокое понимание архитектуры коллекций в Java.
Что такое Unmodifiable Collection
Unmodifiable collection - это оболочка (wrapper) над обычной коллекцией, которая запрещает любые операции модификации (add, remove, clear, set).
List<String> original = new ArrayList<>();
original.add("Java");
// Создание unmodifiable view
List<String> unmodifiable = Collections.unmodifiableList(original);
// Попытка модификации вызовет исключение
unmodifiable.add("Python"); // UnsupportedOperationException!
Как это реализовано
Unmodifiable collection - это не специальный тип коллекции, а декоратор (wrapper pattern).
Spring создает внутренний класс, который оборачивает реальную коллекцию:
// Упрощённая реализация java.util.Collections$UnmodifiableList
private static class UnmodifiableList<E> extends AbstractList<E> {
private final List<? extends E> list;
UnmodifiableList(List<? extends E> list) {
this.list = list;
}
// Методы чтения работают
@Override
public E get(int index) {
return list.get(index);
}
@Override
public int size() {
return list.size();
}
// Методы записи выбрасывают исключение
@Override
public E set(int index, E element) {
throw new UnsupportedOperationException();
}
@Override
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
@Override
public E remove(int index) {
throw new UnsupportedOperationException();
}
@Override
public boolean add(E e) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}
Какое исключение выбрасывается
UnsupportedOperationException - это unchecked исключение, которое выбрасывается при попытке вызвать неподдерживаемую операцию.
List<String> unmodifiable = Collections.unmodifiableList(
Arrays.asList("Java", "Kotlin")
);
try {
unmodifiable.add("Python");
} catch (UnsupportedOperationException e) {
System.out.println("Нельзя модифицировать: " + e.getMessage());
}
// Output: Нельзя модифицировать: null
Разница между Immutable и Unmodifiable
Это важное различие:
// UNMODIFIABLE - только view, оригинал можно изменить
List<String> original = new ArrayList<>();
original.add("Java");
List<String> unmodifiable = Collections.unmodifiableList(original);
// Изменяем оригинал
original.add("Python");
// В unmodifiable это отразится!
System.out.println(unmodifiable.size()); // 2!
unmodifiable.forEach(System.out::println);
// Java
// Python
// Но изменить через unmodifiable нельзя
unmodifiable.add("Ruby"); // UnsupportedOperationException!
Истинная Immutable коллекция (Java 9+)
// Java 9+ - List.of создает ИСТИННО immutable коллекцию
List<String> immutable = List.of("Java", "Kotlin");
// Нельзя изменить
immutable.add("Python"); // UnsupportedOperationException
// Даже оригинальные данные нельзя передать
// List.of создает новый список, не связанный с исходными данными
Практические примеры
1. Защита метода от модификации
public class User {
private List<String> roles = new ArrayList<>();
// НЕПРАВИЛЬНО - может быть модифицирован
public List<String> getRoles1() {
return roles;
}
// ПРАВИЛЬНО - защищено unmodifiable
public List<String> getRoles2() {
return Collections.unmodifiableList(roles);
}
// ЕЩЕ ЛУЧШЕ - Java 9+
public List<String> getRoles3() {
return List.copyOf(roles); // immutable copy
}
}
// Использование
User user = new User();
user.getRoles2().add("ADMIN"); // UnsupportedOperationException!
2. Collections.unmodifiableMap
Map<String, Integer> original = new HashMap<>();
original.put("Java", 25);
original.put("Python", 15);
Map<String, Integer> unmodifiable = Collections.unmodifiableMap(original);
// Попытка модификации
unmodifiable.put("Kotlin", 10); // UnsupportedOperationException
unmodifiable.remove("Java"); // UnsupportedOperationException
3. Collections.unmodifiableSet
Set<String> original = new HashSet<>();
original.add("item1");
original.add("item2");
Set<String> unmodifiable = Collections.unmodifiableSet(original);
for (String item : unmodifiable) {
System.out.println(item); // Это работает
}
unmodifiable.add("item3"); // UnsupportedOperationException
Почему это нужно в архитектуре
- Инкапсуляция - класс контролирует доступ к своему состоянию
public class Configuration {
private final List<String> allowedHosts = new ArrayList<>();
public List<String> getAllowedHosts() {
// Клиент не может случайно изменить конфиг
return Collections.unmodifiableList(allowedHosts);
}
}
- Предотвращение ошибок - раньше ошибка будет выявлена
// Если вызывающий код пытается модифицировать, он сразу узнает
// что это запрещено, вместо того чтобы потом искать баг
List<String> config = service.getConfiguration();
config.add("something"); // Сразу UnsupportedOperationException
- Thread safety - если несколько потоков читают, это безопасно
public class SharedData {
private List<String> data = Collections.unmodifiableList(
new ArrayList<>()
);
// Несколько потоков могут безопасно читать
// Никто не может модифицировать
}
Вывод
UnsupportedOperationException выбрасывается потому что:
- Unmodifiable collection - это декоратор, который явно переопределяет методы модификации
- Каждый метод изменения (add, remove, set, clear) проверяет, является ли коллекция modifiable
- Если попытка изменения, выбрасывается UnsupportedOperationException
- Это дизайн-патерн, который предотвращает случайные модификации защищённых данных
Это хорошая практика использовать unmodifiable коллекции при возврате данных из методов, если нет необходимости в модификации со стороны клиента.