Почему мой немодифицируемый список можно изменить?

Мне нужен List, элементы которого нельзя ни удалить, ни добавить. Я думал, что нашел ответ с помощью Collections.unmodifiableList в Java 8. Я передаю исходный список и возвращаю предположительно неизменяемый список.

Тем не менее, когда я удаляю элемент из исходного списка, мой неизменяемый список изменяется. Что здесь происходит?

См. этот демонстрационный код. Мой неизменяемый список уменьшается с 3 элементов 2 при удалении из оригинала.

String dog = "dog";
String cat = "cat";
String bird = "bird";

List< String > originalList = new ArrayList<>( 3 );
originalList.add( dog );
originalList.add( cat );
originalList.add( bird );

List< String > unmodList = Collections.unmodifiableList( originalList );
System.out.println( "unmod before: " + unmodList );  // Yields [dog, cat, bird]
originalList.remove( cat );  // Removing element from original list affects the unmodifiable list?
System.out.println( "unmod after: " + unmodList );  // Yields [dog, bird]

person Basil Bourque    schedule 02.09.2015    source источник
comment
Хотя у StackOverflow есть похожие вопросы о Collections.unmodifiable…, я не смог найти простого решения этой проблемы. Поэтому я разместил этот вопрос и ответ.   -  person Basil Bourque    schedule 02.09.2015
comment
В Javadoc Collections.unmodifiableList очень ясно, что неизменяемым является только возвращаемый список. Вы действительно ожидали, что этот вызов сделает исходный список неизменяемым?   -  person wero    schedule 03.09.2015
comment
@wero «Javadoc… очень ясен…» — позволю себе не согласиться. Текст документа: «Возвращает неизменяемое представление указанного списка». далеко не ясно об исходной коллекции, поддерживающей новый объект. Особенно непонятно непосвященным, поскольку Java не имеет определенного представления. Быстрый поиск в Google покажет, что я далеко не одинок в этом неправильном прочтении.   -  person Basil Bourque    schedule 03.09.2015


Ответы (1)


Немодифицируемый список поддерживается исходным списком

Этот метод unmodifiableList в < служебный класс href="http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html">Collections не создает новый список, он создает псевдо- список, поддерживаемый исходным списком. Любые попытки добавления или удаления, предпринятые через «неизменяемый» объект, будут заблокированы, поэтому имя соответствует своему назначению. Но на самом деле, как вы показали, исходный список может быть изменен и одновременно влияет на наш вторичный, не совсем неизменяемый список.

Это прописано в документации класса:

Возвращает неизменяемое представление указанного списка. Этот метод позволяет модулям предоставлять пользователям доступ «только для чтения» к внутренним спискам. Операции запроса в возвращаемом списке "прочитаны" до указанного списка, а попытки изменить возвращенный список, будь то напрямую или через его итератор, приводят к исключению UnsupportedOperationException.

Это четвертое слово является ключевым: view. Новый объект списка не является новым списком. Это наложение. Так же, как калька или прозрачная пленка поверх рисунка не позволяет делать пометки на рисунке, но не мешает зайти под ним и изменить исходный рисунок.

Мораль истории: не используйте Collections.unmodifiableList для создания защитных копий списков.

То же самое для Collections.unmodifiableMap, < a href="http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiedSet-java.util.Set-">Collections.unmodifiableSet и т. д.

Google Гуава

Вместо класса Collections для защитного программирования я рекомендую использовать библиотеку Google Guava и ее ImmutableCollections.

Вы можете составить новый список.

public static final ImmutableList<String> ANIMALS = ImmutableList.of(
        dog,
        cat,
        bird );

Или вы можете сделать защитную копию существующего списка. В этом случае вы получите свежий отдельный список. Удаление из исходного списка не повлияет (уменьшит) неизменяемый список.

ImmutableList<String> ANIMALS = ImmutableList.copyOf( originalList ); // defensive copy!

Но помните, хотя собственное определение коллекции является отдельным, содержащиеся в нем объекты являются общими как для исходного списка, так и для нового неизменяемого списка. Создавая эту защитную копию, мы не дублируем объект «собака». В памяти остается только один объект собака, оба списка содержат ссылку, указывающую на одну и ту же собаку. Если свойства в объекте «собака» изменены, обе коллекции указывают на один и тот же объект собаки, поэтому обе коллекции увидят новое значение свойства собаки.

person Basil Bourque    schedule 02.09.2015
comment
Собственно, мораль Истории такова: никогда не храните ссылки на список поддержки. Используйте вспомогательный список/набор/карту непосредственно при инициализации, затем оберните с помощью unmodifiableXxx() и отбросьте исходную ссылку. --- И неизменяемый не делает защитную копию. Это точка зрения, как вы уже сами сказали. - person Andreas; 02.09.2015
comment
Согласен с предыдущим комментарием, мораль этой истории заключается в том, что вы храните ссылки на базовый список в секрете и не делитесь ими... - person assylias; 17.03.2016