Неизменный против неизменяемой коллекции

170

Из обзора структуры коллекций :

Коллекции , которые не поддерживают операции модификации (такие , как add, removeи clear) называются нередактируемым . Коллекции, которые не являются неизменяемыми, являются изменяемыми .

Коллекции, которые дополнительно гарантируют, что никакие изменения в Collectionобъекте не будут видны, называются неизменяемыми . Коллекции, которые не являются неизменяемыми, являются изменяемыми .

Я не могу понять различие.
В чем разница между неизменным и неизменным здесь?

Кратил
источник

Ответы:

207

Немодифицируемая коллекция часто является оболочкой для модифицируемой коллекции, к которой другой код может иметь доступ . Таким образом, хотя вы не можете вносить в него какие-либо изменения, если у вас есть только ссылка на немодифицируемую коллекцию, вы не можете полагаться на то, что содержимое не меняется.

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

В основном, разница в том, сможет ли другой код изменить коллекцию за вашей спиной.

Джон Скит
источник
64
Неизменяемая коллекция не гарантирует, что ничто больше не может измениться. Это просто гарантирует, что сама коллекция не может быть изменена (и не путем переноса, а путем копирования). Объекты, которые присутствуют в коллекции, все еще могут быть изменены, и на них не дается никаких гарантий.
Hiery Nomus
8
@ HieryNomus: Обратите внимание, что я не сказал, что ничего не может измениться - я сказал, что ничто не может изменить коллекцию.
Джон Скит
1
хорошо, возможно, неправильно это понял;) Но хорошо бы это уточнить.
Hiery Nomus
5
Итак, что вы говорите. Для истинной неизменности вам нужна неизменяемая коллекция, содержащая элементы неизменяемого типа.
Эван Плейс
1
@savanibharat: это зависит от того, есть ли путь к коду, который еще можно изменить list. Если что-то может позже позвонить, list.add(10)то collотразит это изменение, поэтому нет, я бы не назвал это неизменным.
Джон Скит
92

Собственно, unModifiableколлекция - это представление, поэтому косвенно она все еще может быть «модифицирована» из некоторой другой ссылки, которая может быть изменена. Кроме того, это просто представление только для чтения другой коллекции, когда исходная коллекция изменяется, unModifiable Collection всегда будет отображать последние значения.

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

Вот тестовый пример, чтобы визуализировать эту разницу.

@Test
public void testList() {

    List<String> modifiableList = new ArrayList<String>();
    modifiableList.add("a");

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("--");


    //unModifiableList

    assertEquals(1, modifiableList.size());

    List<String> unModifiableList=Collections.unmodifiableList(
                                        modifiableList);

    modifiableList.add("b");

    boolean exceptionThrown=false;
    try {
        unModifiableList.add("b");
        fail("add supported for unModifiableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("unModifiableList.add() not supported");
    }
    assertTrue(exceptionThrown);

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);

    assertEquals(2, modifiableList.size());
    assertEquals(2, unModifiableList.size());
            System.out.println("--");



            //immutableList


    List<String> immutableList=Collections.unmodifiableList(
                            new ArrayList<String>(modifiableList));

    modifiableList.add("c");

    exceptionThrown=false;
    try {
        immutableList.add("c");
        fail("add supported for immutableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("immutableList.add() not supported");
    }
    assertTrue(exceptionThrown);


    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);
    System.out.println("immutableList:"+immutableList);
    System.out.println("--");

    assertEquals(3, modifiableList.size());
    assertEquals(3, unModifiableList.size());
    assertEquals(2, immutableList.size());

}

Вывод

modifiableList:[a]
--
unModifiableList.add() not supported
modifiableList:[a, b]
unModifiableList:[a, b]
--
immutableList.add() not supported
modifiableList:[a, b, c]
unModifiableList:[a, b, c]
immutableList:[a, b]
--
Прашант Бхате
источник
Я не вижу никакой разницы. Можете ли вы указать, насколько неизменным является другой? Я вижу, что как неизменяемые, так и неизменяемые выдают ошибку, и добавление не поддерживается. Я что-то здесь упускаю?
AKS
2
@AKS Пожалуйста, смотрите вывод трех последних записей списка после добавления 'c' в список, в то время как размер modifiableListи unModifiableListувеличенный immutableListразмер не изменились
Prashant Bhate
1
Ой! понял! :) .. Итак, здесь вы модифицировали unmodifableList, используя изменения в modifiableList, но ImmutableList не может быть изменен. Но так же, как вы можете изменить ImmutableList, я думаю, что здесь клиент получит доступ только к ссылке ImmutableList, а ссылка на modifiableList, с помощью которой создается ImmutableList, не будет представлена ​​клиенту. право?
AKS
1
да, потому что нет ссылки на new ArrayList<String>(modifiableList)immutableList, который нельзя изменить
Prashant Bhate
@PrashantBhate Привет, нет ссылки на new ArrayList<String>(modifiableList)из-за new? Спасибо.
Unheilig
11

Я думаю, что основное отличие состоит в том, что владелец изменяемой коллекции может захотеть предоставить доступ к коллекции к какому-либо другому коду, но обеспечить такой доступ через интерфейс, который не позволяет другому коду изменять коллекцию (сохраняя эту возможность на владение кодом). Таким образом, коллекция не является неизменной, но некоторым пользователям не разрешается изменять коллекцию.

В руководстве Oracle по Java Collection Wrapper сказано следующее (выделение добавлено):

Немодифицируемые обертки имеют два основных назначения:

  • Сделать коллекцию неизменной после ее создания. В этом случае рекомендуется не сохранять ссылку на резервную копию. Это абсолютно гарантирует неизменность.
  • Разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на резервную копию, но раздаете ссылку на упаковщик. Таким образом, клиенты могут смотреть, но не изменять, пока вы поддерживаете полный доступ .
Майкл Берр
источник
3

Если мы говорим о JDK Unmodifiable*против гуавы Immutable*, на самом деле разница также в производительности . Неизменяемые коллекции могут быть как более быстрыми, так и более эффективными, если они не являются обертками вокруг обычных коллекций (реализации JDK являются обертками). Ссылаясь на команду гуавы :

JDK предоставляет методы Collections.unmodifiableXXX, но, по нашему мнению, они могут быть

<...>

  • неэффективно: структуры данных по-прежнему имеют все накладные расходы на изменяемые коллекции, включая одновременные проверки изменений, дополнительное пространство в хеш-таблицах и т. д.
Dmide
источник
думая о производительности, вы должны также принять во внимание, что неизменяемая оболочка не копирует коллекцию, поскольку в качестве неизменной версии, используемой в guava, а теперь и в jdk9 + с eg List.of(...), действительно, копируется дважды!
Бенц
2

Цитировать учебники по Java ™ :

В отличие от оболочек синхронизации, которые добавляют функциональность к обернутой коллекции, неизменяемые оболочки отбирают функциональность. В частности, они лишают возможности изменять коллекцию, перехватывая все операции, которые могли бы изменить коллекцию, и выбрасывая исключение UnsupportedOperationException . Немодифицируемые обертки имеют два основных назначения:

  • Сделать коллекцию неизменной после ее создания. В этом случае рекомендуется не сохранять ссылку на резервную копию. Это абсолютно гарантирует неизменность.

  • Разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на резервную копию, но раздаете ссылку на упаковщик. Таким образом, клиенты могут смотреть, но не изменять, пока вы поддерживаете полный доступ.

(акцент мой)

Это действительно подводит итог.

Бхарат
источник
1

Как отмечено выше, немодифицируемое не похоже на неизменяемое, потому что немодифицируемое собрание может быть изменено, если, например, немодифицируемая коллекция имеет базовую коллекцию делегатов, на которую ссылается какой-либо другой объект, и этот объект изменяет ее.

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

Предполагая, что мы можем сделать это, есть также концепция, безопасная для потоков. И вы можете быть уверены, что неизменяемость (или неизменяемость во времени) также подразумевает безопасность потоков. Однако это не тот случайи это главное замечание, которое я здесь делаю, которое еще не было отмечено в других ответах. Я могу создать неизменный объект, который всегда возвращает одинаковые результаты, но не является поточно-ориентированным. Чтобы увидеть это, предположим, что я создаю неизменную коллекцию, поддерживая добавления и удаления с течением времени. Теперь неизменяемая коллекция возвращает свои элементы, просматривая внутреннюю коллекцию (которая может меняться со временем), а затем (внутренне) добавляя и удаляя элементы, которые были добавлены или удалены после создания коллекции. Ясно, что хотя коллекция всегда будет возвращать одни и те же элементы, она не является поточно-ориентированной, просто потому, что никогда не изменит значение.

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

дан б
источник
1

Учебные руководства Java ™ говорят следующее:

В отличие от оболочек синхронизации, которые добавляют функциональность к обернутой коллекции, неизменяемые оболочки отбирают функциональность. В частности, они лишают возможности изменять коллекцию, перехватывая все операции, которые могли бы изменить коллекцию, и выбрасывая исключение UnsupportedOperationException. Немодифицируемые обертки имеют два основных назначения:

Сделать коллекцию неизменной после ее создания. В этом случае рекомендуется не сохранять ссылку на резервную копию. Это абсолютно гарантирует неизменность.

Разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на резервную копию, но раздаете ссылку на упаковщик. Таким образом, клиенты могут смотреть, но не изменять, пока вы поддерживаете полный доступ.

Я думаю, что это достаточно хорошее объяснение, чтобы понять разницу.

piyush121
источник