不可变与不可修改的集合

从集合框架概述 :

不支持修改操作的集合(如addremoveclear )被称为不可修改 。 不可修改的集合是可修改的

另外保证Collection对象中没有变化的Collection被称为不可变的 。 不可变的集合是可变的

我无法理解这个区别。
这里不可 修改不可变的区别是什么?

一个不可修改的集合通常是一个可修改集合的包装,其他代码仍然可以访问 。 所以,如果你只有一个不可修改的集合的引用, 不能做任何改变,你不能依赖不改变的内容。

一个不可变的集合保证没有任何东西可以改变集合了。 如果它包装一个可修改的集合,它确保没有其他代码可以访问该可修改的集合。 请注意,虽然没有代码可以更改集合包含引用的对象,但是对象本身可能仍然是可变的 – 创build一个不可变的StringBuilder集合不会以某种方式“冻结”这些对象。

基本上,区别在于其他代码是否可以更改背后的集合。

基本上unModifiable集合是一个视图,所以间接它仍然可以从其他可修改的引用“修改”。 另外作为其他集合的只读视图 ,当源集合更改时,unModifiable集合将始终以最新值显示。

然而, immutable Collection可以被看作是另一个集合的只读副本 ,不能被修改。 在这种情况下,当源集合发生更改时,不可变集合不会反映这些更改

这里是一个testing用例,用来可视化这个区别。

 @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] -- 

我认为主要的区别在于,可变集合的所有者可能希望提供对其他代码的访问权限,但是通过一个接口提供访问权限,不允许其他代码修改集合(同时保留该能力到拥有的代码)。 所以集合不是不可变的,但某些用户不允许更改集合。

甲骨文的Java集合包装教程有这个说(强调增加):

不可修改的包装有两个主要用途,如下所示:

  • 一旦build立了一个集合就不可变。 在这种情况下,最好不要保留对后备集合的引用。 这绝对保证不变性。
  • 允许某些客户端只读访问您的数据结构。 您保留对后备集合的引用,但是分发引用。 这样,客户可以看,但不能修改,而你保持完全访问

如果我们正在谈论JDK Unmodifiable* vs番石榴Immutable* ,实际上差异也是在性能上 。 如果不可变集合不是常规集合的包装(JDK实现是包装器),那么它们既可以更快,也更具有内存效率。 援引番石榴队 :

JDK提供了Collections.unmodifiableXXX方法,但在我们看来,这些可以

<…>

  • 效率低下: 数据结构仍然具有可变集合的所有开销,包括并发修改检查,散列表中的额外空间等。

引用java docs,与向包装集合添加function的同步包装不同,不可修改的包装将function移走。 特别是, 它们通过拦截所有将修改集合并抛出UnsupportedOperationException的操作来取消修改集合的能力

不可修改的包装有两个主要用途,如下所示:

一旦build立了一个集合就不可变。 在这种情况下,最好不要保留对后备集合的引用。 这绝对保证了不变性。

允许某些客户端只读访问您的数据结构。 您保留对后备集合的引用,但是分发引用。 这样,客户可以看,但不能修改,而你保持完全访问。

这真的是总结。

如上所述,不可修改并不像不可修改的,因为如果一个不可修改的集合具有被某个其他对象引用的基础委托集合,并且该对象改变了它,则不可修改的集合可以被改变。

关于不可变的,它甚至没有很好的定义。 但是,通常意味着对象“不会改变”,但是这需要recursion地定义。 例如,我可以定义不变的类,其实例variables都是基元,其方法都不包含参数和返回基元。 然后,这些方法recursion地允许实例variables是不可变的,并且所有方法都包含不可变的参数,并返回不可变的值。 这些方法应该保证随着时间的推移返回相同的值。

假设我们可以做到这一点,也有线程安全的概念。 而且你可能会被认为是不可变的(或者不会随着时间的推移而改变)也意味着线程安全。 然而,情况并非如此 ,这是我在这里提出的主要观点,其他答案中还没有提到。 我可以构造一个总是返回相同结果但不是线程安全的不可变对象。 为了看到这一点,假设我通过随着时间的推移增加和删除来构build一个不可变的集合。 现在,不可变集合通过查看内部集合(可能随时间而改变),然后(内部)添加和删除集合创build之后添加或删除的元素,返回其元素。 显然,尽pipe集合总是返回相同的元素,但它不是线程安全的,因为它永远不会改变值。

现在我们可以将不可变的定义为线程安全的对象,永远不会改变。 关于创build不可变类的指导原则通常会导致这样的类,但是请记住,可能有办法创build不可变类,这需要关注线程安全性,例如上面的“快照”收集示例中所述。