Java不可变集合


115

Java 1.6 Collection Framework文档中

不支持任何修改操作(如类别addremoveclear)被称为不可修改的。[...]额外保证Collection对象中的任何更改都将不可见的collection被称为不可变的

第二个标准使我有些困惑。鉴于第一个集合是不可修改的,并且假定原始集合引用已被删除,那么第二行中提到的更改是什么?它是指集合中包含的元素的变化,即元素的状态吗?

第二个问题:
要使集合不可变,该如何提供指定的其他保证人?如果集合中元素的状态由线程更新,那么对于不可变是否足以使状态中的那些更新在保存不可变集合的线程上不可见?

为了使集合不可变,如何提供指定的附加保证?


在一般情况下(尤其是在功能语言中),不可变(也称为持久性)集合可能会在某种意义上发生更改,即您可以获取此集合的新状态,但与此同时,状态仍可通过其他链接使用。例如,newCol = oldCol.add("element")将产生新的集合,该集合是具有1个以上元素的旧集合的副本,并且所有对的引用oldCol仍将指向相同的未更改的旧集合。
ffriend

Answers:


154

不可修改的集合通常是其他集合的只读视图(包装器)。您不能添加,删除或清除它们,但是基础集合可以更改。

不可变集合完全不能更改-它们不包装另一个集合-它们具有自己的元素。

这是番石榴的报价 ImmutableList

与不同Collections.unmodifiableList(java.util.List<? extends T>),后者是一个仍可以更改的单独集合的视图,ImmutableList与之不同,的实例包含其自己的私有数据,并且永远不会更改。

因此,基本上,为了从可变的集合中获取不可变的集合,您必须将其元素复制到新集合中,并禁止所有操作。


@Bhaskar-参见我的最后一段。
2011年

1
如果包装在另一个不可修改集合中的集合没有其他引用,那么该不可修改集合的行为与不可变集合完全相同吗?
Bhaskar

1
@Bozho,如果未提供对后备集合的引用,并且仅提供了不可修改的集合包装器引用,则无法更改它。那你为什么说“你不确定”?它是否指出由于支持集合可修改而导致某些线程或以某种方式被修改的情况?
AKS

@AKS:当包装了一个集合时unmodifiableList,仅接收对该列表的引用的代码将无法对其进行修改,但是任何具有对原始列表的引用并且可以在创建包装器之前对其进行修改的代码仍将之后能够这样做。如果创建原始列表的代码知道存在于该列表中的每个引用所发生的事情,并且知道它们中没有一个将落入可能修改该列表的代码手中,那么它可以知道该列表永远不会改性。如果引用是从外部代码收到的,则...
supercat 2013年

……没有办法unmodifiableList,也没有使用它的代码来知道包装后的集合是否或如何发生变化。
超级猫

86

不同之处在于您无法引用允许更改的不可变集合。不可修改的集合无法通过该引用进行修改,但是某些其他对象可能指向可以更改其的相同数据。

例如

List<String> strings = new ArrayList<String>();
List<String> unmodifiable = Collections.unmodifiableList(strings);
unmodifiable.add("New string"); // will fail at runtime
strings.add("Aha!"); // will succeed
System.out.println(unmodifiable);

1
如果没有使用原始集合引用来修改基础集合,那么有什么各种原因(如果有)会带来无法修改的集合变化?
Bhaskar

21
Collection<String> c1 = new ArrayList<String>();
c1.add("foo");
Collection<String> c2 = Collections.unmodifiableList(c1);

c1可变的(即,既不可更改也不可更改)。
c2不可修改的:它不能改变自己,但如果后来我改变c1变化将是可见的c2

这是因为c2它只是一个包装,c1而不是真正的独立副本。Guava提供了ImmutableList界面和一些实现。这些工作实际上是创建输入的副本(除非输入本身是不可变的集合)。

关于第二个问题:

集合的可变性/不变性取决于其中包含的对象的可变性/不变性。修改包含在集合中的对象并不能算作一个这个描述“收集的修改”。当然,如果您需要一个不可变的集合,通常还希望它包含不可变的对象。


2
@约翰:不,不是。至少不符合OP引用的定义。
约阿希姆·绍尔

@JohnVint,真的吗?我不这么认为,因为使用c1,我仍然可以向其中添加元素,并且此添加对c2可见。那不是意味着它不是一成不变的吗?
Bhaskar

好吧,我想如果c1可以逃脱,那么它就不会是不变的,但是不变性也指的是集合中的内容。我更多是指java.util.Date的unmodifiableCollection
John Vint

1
@Vint:“如果c1不逃避”不是在规范中任何地方都可以区分的考虑因素。我认为,如果可以以使其不可变的方式使用集合,那么您应该始终将其视为不可变的。
约阿希姆·绍尔

1
让我引用一下定义:“额外保证 ……的藏书”(强调我的意思)。Collection.unmodifiableList() 不能保证这一点,因为它不能保证其参数不会转义。番石榴ImmutableList.of 总是产生不可变的List,即使您让它的参数转义。
约阿希姆·绍尔

17

现在,java 9具有不可变列表,集合,映射和Map.Entry的工厂方法。

在Java SE 8和更早版本中,我们可以使用诸如unmodifiableXXX之类的Collections类实用程序方法来创建Immutable Collection对象。

但是,这些Collections.unmodifiableXXX方法非常繁琐且冗长。为了克服这些缺点,Oracle公司在List,Set和Map接口中添加了一些实用程序方法。

现在在Java 9中: -List和Set接口具有“ of()”方法来创建一个空的或不空的不可变List或Set对象,如下所示:

空列表示例

List immutableList = List.of();

非空清单示例

List immutableList = List.of("one","two","three");

2
在Java 10中List.copyOfSet.copyOf已经添加了Java 10 ,可以创建列表/集合的不可修改的副本,或者如果给定的集合已经不可修改,则返回给定的集合,请参见JDK-8191517
Marcono1234 '19

6

我相信这里的意思是,即使集合是不可修改的,也不能保证它不能更改。例如,如果某个元素过旧,则将其逐出一个集合。不可修改只是意味着持有引用的对象不能更改它,而不是它不能更改。一个真正的例子就是Collections.unmodifiableList方法。它返回列表的不可修改的视图。传递给此方法的List引用仍然可以修改,因此该列表可以由传递的引用的任何持有者修改。这可能导致ConcurrentModificationExceptions和其他不良情况。

不可变,表示绝不能更改集合。

第二个问题:不可变集合并不意味着集合中包含的对象不会发生变化,只是该集合所持有的对象的数量和组成不会发生变化。换句话说,集合的参考列表不会改变。这并不意味着所引用对象的内部不能改变。


好一个!因此,如果我在Unmodifiable Collection上创建包装器并将可修改集合引用设为私有,那么我可以确定它是不可变的。对?
AKS

如果我了解您的问题,那么答案是否定的。包装不可修改对象并不能保护它不被传递给集合unmodifiableList。如果要使用此功能ImmutableList
约翰B

0

Pure4J通过两种方式支持您所追求的。

首先,它提供一个@ImmutableValue注释,以便您可以注释一个类以说它是不可变的。有一个maven插件,可让您检查代码实际上是不可变的(使用final等等)。

其次,它提供Clojure的持久性集合(带有附加的泛型),并确保添加到集合中的元素是不可变的。这些的性能显然还不错。集合都是不可变的,但是实现了Java集合接口(和泛型)进行检查。变异会返回新的集合。

免责声明:我是这个的开发商

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.