Почему мой `unmodifiableList` модифицируется?

Я хочу 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]

1 ответ

Решение

UnmodifiableList поддерживается оригинальным списком

Тот unmodifiableList метод в Collections Утилита класса не создает новый список, она создает псевдотренинг, за которым стоит исходный список. Любые попытки добавления или удаления, сделанные с помощью "неизменяемого" объекта, будут заблокированы, поэтому имя соответствует своему назначению. Но действительно, как вы показали, исходный список может быть изменен и одновременно влияет на наш вторичный, не совсем неизменяемый список.

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

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

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

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

То же самое для Collections.unmodifiableMap, Collections.unmodifiableSet, и так далее.

Google Guava

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

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

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

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

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

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

Другие вопросы по тегам