不可变与不可修改的集合

集合框架概述:

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

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

我不明白其中的区别 这里的无法改变的不可变的有什么区别?< / p >

88217 次浏览

不可修改的集合通常是可修改集合还有哪些代码可以访问的包装器。因此,虽然如果你只有一个不可修改集合的引用,不能对它做任何更改,但你不能依赖于内容不变。

不可变的集合保证没有什么可以再修改集合。如果它包装了一个可修改的集合,它将确保没有其他代码可以访问该可修改的集合。注意,虽然没有代码可以改变集合包含引用的对象,但对象本身仍然可能是可变的——创建StringBuilder的不可变集合并不会以某种方式“冻结”这些对象。

基本上,区别在于其他代码是否能够在您背后更改集合。

我认为主要的区别是,可变集合的所有者可能希望向其他一些代码提供对集合的访问,但通过不允许其他代码修改集合的接口提供这种访问(同时将这种能力保留给所有者代码)。因此集合不是不可变的,但某些用户不允许更改集合。

Oracle的Java集合包装器教程是这样说的(强调添加):

不可修改的包装器有以下两个主要用途:

  • 使一个集合在构建后变为不可变的。在这种情况下,最好不要维护对后台的引用 收集。这绝对保证了不可变性
  • 允许某些客户端只读访问您的数据结构。您保留了对备份集合的引用 输出对包装器的引用。这样,客户可以看,但不能看 修改,同时保持完全访问.
  • .

基本上unModifiable Collection是一个视图,所以它仍然可以间接地从其他一些可修改的引用中“修改”。同样,由于它只是另一个集合的< em > < / em >只读的视图,当源集合发生变化时,不可修改集合将始终显示最新值。

然而,immutable集合可以被视为另一个集合的< em > < / em >只读的副本,不能被修改。在这种情况下,当源集合发生变化时,不可变集合不反映这些变化

下面是一个测试用例来可视化这种差异。

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

如上所述,不可修改的不像不可变的,因为不可修改的集合可以被改变,例如,如果一个不可修改的集合有一个被其他对象引用的底层委托集合,而该对象改变了它。

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

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

现在我们可以将不可变定义为线程安全且永远不会改变的对象。有一些创建不可变类的指导原则,这些类通常会导致这样的类,然而,请记住,可能有一些创建不可变类的方法,需要注意线程安全,例如,如上面的“快照”集合示例所述。

如果我们谈论的是JDK Unmodifiable*和番石榴Immutable*,实际上区别也在性能。如果不可变集合是常规集合的包装器(JDK实现是包装器),那么它们可以更快,更节省内存。 以番石榴团队为例: < / p >

JDK提供了集合。不可修改的xxx方法,但在我们看来,这些可以

& lt;…>

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

引用Java™教程:

同步包装器将功能添加到被包装的集合中,而不可修改的包装器则将功能带走。特别是它们通过拦截将修改集合的所有操作并抛出UnsupportedOperationException来剥夺修改集合的能力。不可修改的包装器有以下两个主要用途:

  • 使集合在构建后不可变。在这种情况下,最好不要维护对支持集合的引用。这绝对保证了不变性。

  • 允许某些客户端只读访问您的数据结构。您保留了对支持集合的引用,但分发了对包装器的引用。通过这种方式,客户端可以查看但不能修改,而您可以保持完全访问。

(强调我的)

这真的很概括。

Java™教程介绍了以下内容:

与同步包装器不同,同步包装器将功能添加到 被包装的集合,不可修改的包装器会带走功能。 特别是,它们剥夺了通过修改集合的能力 拦截所有修改集合的操作 抛出UnsupportedOperationException。不可修改的包装 两种主要用途如下:

使一个集合在构建后变为不可变的。在这种情况下, 最好不要维护对后台的引用 收集。这绝对保证了不可变性 允许某些客户端只读访问您的数据结构。你 保留对支持集合的引用,但分发对的引用 包装器。通过这种方式,客户端可以查看但不能修改,而您

.保持完全访问

我认为这是一个很好的解释,足以理解其中的区别。

[不可修改且不可更改] .

不可修改的集合(对象)仍然可以通过改变原始对象来改变。使用参考是可能的。

Java提供了几种方法来创建一个不可修改的映射:

  • Collections.unmodifiableMap()
  • Java 9 Map.of()Map.ofEntries()
    // 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…包装器。然而,这些集合不是包装器——它们是由类实现的数据结构,任何修改数据的尝试都会引发异常。

如果您创建了一个List并将其传递给集合。unmodifiableelist方法,那么你会得到一个不可修改的视图。底层列表仍然是可修改的,通过返回的list可以看到对它的修改,因此它实际上不是不可变的。

要演示此行为,请创建一个List并将其传递给Collections.unmodifiableList。如果尝试直接添加到不可修改的列表,则抛出UnsupportedOperationException。

但是,如果您更改了原始List,则不会生成错误,并且不可修改的List已被修改。

在这种情况下,要使集合在构建后不可变,最好不要维护对支持集合的引用。这绝对保证了不变性。

此外,允许某些客户端只读访问您的数据结构。您可以保留对支持集合的引用,但可以分发对包装器的引用。通过这种方式,客户端可以查看但不能修改,而您保持完全访问权限。

因此,不可变集合可以包含可变对象,如果这样做,则该集合既不是不可变的,也不是线程安全的。

不可修改的和不可变的集合

创建一个可修改的地图

Map<String, String> modifiableMap = new HashMap();
modifiableMap.put(“1”,”one”);
modifiableMap.put(“2”,”two”);
modifiableMap.put(“3”,”three”);

从modifiableMap中创建一个unmodifiableMap

 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.
     

从modifiableMap创建一个immutableMap

 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.