Неизменный против неизменяемой коллекции
Из обзора структуры коллекций:
Коллекции, которые не поддерживают операции модификации (такие как
add
,remove
а такжеclear
) называются неизменяемыми. Коллекции, которые не являются неизменяемыми, являются изменяемыми.Коллекции, которые дополнительно гарантируют, что никаких изменений в
Collection
Объект будет видимым, называется неизменным. Коллекции, которые не являются неизменяемыми, являются изменяемыми.
Я не могу понять различие.
В чем разница между неизменным и неизменным здесь?
11 ответов
Немодифицируемая коллекция часто является оболочкой для модифицируемой коллекции, к которой другой код может иметь доступ. Таким образом, хотя вы не можете вносить в него какие-либо изменения, если у вас есть только ссылка на немодифицируемую коллекцию, вы не можете полагаться на то, что содержимое не меняется.
Неизменная коллекция гарантирует, что ничто не может изменить коллекцию больше. Если он переносит изменяемую коллекцию, он удостоверяется, что никакой другой код не имеет доступа к этой изменяемой коллекции. Обратите внимание, что хотя никакой код не может изменить объекты, на которые ссылается коллекция, сами объекты могут быть изменяемыми, создавая неизменную коллекцию StringBuilder
не как-то "заморозить" эти объекты.
По сути, разница в том, сможет ли другой код изменить коллекцию за вашей спиной.
В принципе 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]
--
Я думаю, что основное отличие состоит в том, что владелец изменяемой коллекции может захотеть предоставить доступ к коллекции к какому-либо другому коду, но обеспечить такой доступ через интерфейс, который не позволяет другому коду изменять коллекцию (сохраняя эту возможность на владение кодом). Таким образом, коллекция не является неизменной, но некоторым пользователям не разрешается изменять коллекцию.
В руководстве Oracle по Java Collection Wrapper сказано следующее (выделено):
Немодифицируемые обертки имеют два основных назначения:
- Сделать коллекцию неизменной после ее создания. В этом случае хорошей практикой является не сохранять ссылку на резервную коллекцию. Это абсолютно гарантирует неизменность.
- Разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на резервную коллекцию, но раздаете ссылку на обертку. Таким образом, клиенты могут просматривать, но не изменять, пока вы поддерживаете полный доступ.
// normal list
List list1 = new ArrayList();
list1.add(1);
// unmodifiable list
List list2 = Collections.unmodifiableList(list1);
// immutable list
List list3 = Collections.unmodifiableList(new ArrayList<>(list1));
list1.add(2);
list1.add(3);
System.out.println(list1);
System.out.println(list2);
System.out.println(list3);
Вывод:
[1, 2, 3]
[1, 2, 3]
[1]
Объект считается неизменным, если его состояние не может измениться после создания.После создания неизменяемого экземпляра коллекции в нем хранятся одни и те же данные, пока существует ссылка на него.
Одним из преимуществ неизменяемой коллекции является то, что она автоматически потокобезопасна.Коллекции, содержащие неизменяемые объекты, автоматически становятся потокобезопасными после построения. После создания такой коллекции вы можете передать ее нескольким потокам, и все они будут видеть согласованное представление.
Однако неизменный набор объектов - это не то же самое, что набор неизменяемых объектов.Если содержащиеся элементы являются изменяемыми, это может привести к непоследовательному поведению коллекции или к изменению ее содержимого.
Проще говоря, если вы добавите немного неизменяемости к чему-то изменчивому, вы получите изменчивость. А если вы добавите немного изменчивости к чему-то неизменному, вы получите изменчивость.
Неизменяемое и неизменяемое - не одно и то же:
Неизменяемые коллекции ведут себя так же, как и оболочки Collections.unmodifiable... Однако эти коллекции не являются оболочками - это структуры данных, реализованные классами, где любая попытка изменить данные вызывает исключение.
Если вы создадите список и передадите его методу Collections.unmodifiableList, вы получите неизменяемое представление. Базовый список по-прежнему может быть изменен, и изменения в нем видны через возвращаемый список, поэтому на самом деле он не является неизменяемым.
Чтобы продемонстрировать это поведение, создайте список и передайте его в Collections.unmodifiableList. Если вы попытаетесь напрямую добавить в этот неизменяемый список, будет выброшено исключение UnsupportedOperationException.
Но, если вы измените исходный список, ошибка не будет создана, и неизменяемый список будет изменен.
В этом случае, чтобы сделать коллекцию неизменной после ее создания, рекомендуется не поддерживать ссылку на резервную коллекцию. Это абсолютно гарантирует неизменность.
Кроме того, чтобы разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы можете сохранить ссылку на коллекцию резервных копий, но передать ссылку на оболочку. Таким образом, клиенты могут просматривать, но не могут изменять, в то время как вы сохраняете полный доступ.
Таким образом, неизменяемая коллекция может содержать изменяемые объекты, и если это так, коллекция не является ни неизменной, ни потокобезопасной.
Цитировать учебники по Java™:
В отличие от оболочек синхронизации, которые добавляют функциональность к обернутой коллекции, неизменяемые оболочки убирают функциональность. В частности, они лишают возможности изменять коллекцию, перехватывая все операции, которые могли бы изменить коллекцию, и выбрасывая исключение UnsupportedOperationException. Немодифицируемые обертки имеют два основных назначения:
Сделать коллекцию неизменной после ее создания. В этом случае хорошей практикой является не сохранять ссылку на резервную коллекцию. Это абсолютно гарантирует неизменность.
Разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на резервную коллекцию, но раздаете ссылку на обертку. Таким образом, клиенты могут просматривать, но не изменять, пока вы поддерживаете полный доступ.
(акцент мой)
Это действительно подводит итог.
Если мы говорим о JDK Unmodifiable*
против гуавы Immutable*
На самом деле разница также в производительности. Неизменяемые коллекции могут быть как более быстрыми, так и более эффективными в отношении памяти, если они не являются обертками вокруг обычных коллекций (реализации JDK являются обертками). Ссылаясь на команду гуавы:
JDK предоставляет методы Collections.unmodifiableXXX, но, по нашему мнению, они могут быть
<...>
- неэффективно: структуры данных по-прежнему имеют все накладные расходы на изменяемые коллекции, включая параллельные проверки изменений, дополнительное пространство в хеш-таблицах и т. д.
Неизменяемая против неизменяемой коллекции
Создать модифицируемую карту
Map<String, String> modifiableMap = new HashMap();
modifiableMap.put(“1”,”one”);
modifiableMap.put(“2”,”two”);
modifiableMap.put(“3”,”three”);
Создайте неизменяемую карту из modifiableMap
Map<String,String> unmodifiableMap = Collections.unmodifiableMap(modifiableMap);
unmodifiableMap.put(“4”,”Four”) ==>Exception
modifiableMap.put(“4”,”Four”); ==>Allowed, this will also reflect now in the unmodifiableMap , because unmodifiableMap() returns a wrapper around modifiableMap.
Создайте immutableMap из modifiableMap
Map<String,String> immutableMap = Collections.immutableMap(modifiableMap);
immutableMap.put(“5”,”Five”) ==>Exception
modifiableMap.put(“5”,”Five”); ==>Allowed, BUT this will NOT reflect now in the immutableMap, because immutableMap() returns a copy of the modifiableMap.
В Руководствах по Java™ говорится следующее:
В отличие от оболочек синхронизации, которые добавляют функциональность обернутой коллекции, неизменяемые оболочки лишают функциональность. В частности, они лишают возможности изменять коллекцию, перехватывая все операции, которые могут изменить коллекцию, и генерируют исключение UnsupportedOperationException. У немодифицируемых оберток есть два основных применения, а именно:
Чтобы сделать коллекцию неизменной после ее создания. В этом случае рекомендуется не поддерживать ссылку на коллекцию резервных копий. Это абсолютно гарантирует неизменность.
Чтобы разрешить определенным клиентам доступ только для чтения к вашим структурам данных. Вы сохраняете ссылку на коллекцию поддержки, но передаете ссылку на оболочку. Таким образом, клиенты могут просматривать, но не изменять, в то время как вы сохраняете полный доступ.
Я думаю, это достаточно хорошее объяснение, чтобы понять разницу.
Как отмечено выше, немодифицируемое не похоже на неизменяемое, потому что немодифицируемое собрание может быть изменено, если, например, немодифицируемая коллекция имеет базовую коллекцию делегатов, на которую ссылается какой-либо другой объект, и этот объект изменяет ее.
Что касается неизменности, это даже не четко определены. Однако, как правило, это означает, что объект "не изменится", но это должно быть определено рекурсивно. Например, я могу определить неизменяемый для классов, чьи переменные экземпляра являются примитивами и чьи методы не содержат аргументов и возвращают примитивы. Затем методы рекурсивно позволяют переменным экземпляра быть неизменными, а все методы содержат аргументы, которые являются неизменяемыми и возвращают неизменные значения. Методы должны гарантированно возвращать одно и то же значение с течением времени.
Предполагая, что мы можем сделать это, есть также концепция, безопасная для потоков. И вы можете быть уверены, что неизменность (или не изменение со временем) также подразумевает многопоточность. Однако это не тот случай, и это основной момент, который я здесь подчеркиваю и который еще не был отмечен в других ответах. Я могу создать неизменный объект, который всегда возвращает одинаковые результаты, но не является поточно-ориентированным. Чтобы увидеть это, предположим, что я создаю неизменную коллекцию, поддерживая добавления и удаления с течением времени. Теперь неизменяемая коллекция возвращает свои элементы, просматривая внутреннюю коллекцию (которая может изменяться со временем), а затем (внутренне) добавляя и удаляя элементы, которые были добавлены или удалены после создания коллекции. Ясно, что хотя коллекция всегда будет возвращать одни и те же элементы, она не является поточно-ориентированной, просто потому, что никогда не изменит значения.
Теперь мы можем определить неизменяемость как объекты, которые являются потокобезопасными и никогда не изменятся. Существуют рекомендации по созданию неизменяемых классов, которые обычно приводят к таким классам, однако следует помнить, что могут существовать способы создания неизменяемых классов, которые требуют внимания к безопасности потоков, например, как описано в примере коллекции "снимок" выше.
Неизменяемую коллекцию (объект) можно изменить, изменив объект-источник. Можно по ссылке.
Java предоставляет несколько способов создания неизменяемой карты:
Collections.unmodifiableMap()
- Java 9
Map.of()
,Map.ofEntries()