谷歌的ImmutableList和Collections.unmodifiableList()有什么区别?

62

来自ImmutableList javadocs:

与Collections.unmodifiableList(java.util.List)不同,后者是对可以更改的单独集合的视图,而ImmutableList实例包含其自己的私有数据且永远不会更改。 ImmutableList适用于公共静态常量列表以及允许您轻松地为调用方提供的列表创建“防御性副本”的情况。

这是否意味着:

  1. 如果我有一个Dimension对象的ImmutableList(例如),那么我不能更改其中任何Dimension对象吗?
  2. 如果我有Dimension对象的Collections.unmodifiableList(list),那么我只能添加或删除任何对象,但是我可以更改它们(例如调用setDimension(width,height)方法)吗?
答案是:
1. 是的,如果你有一个Dimension对象的ImmutableList,那么你不能更改其中任何一个Dimension对象。 2. 不完全正确。如果你有一个Dimension对象的Collections.unmodifiableList,你不能添加或删除其中的任何对象,也不能更改它们的值。因此,调用setDimension(width,height)方法将导致UnsupportedOperationException异常。

5个回答

63
不,只有集合中对象的数量和引用是不可变的,但这并没有解决你放入集合中的对象本身的可变性问题。使用ImmutableList相比于标准JDK中的Collections.unmodifiableList方法,能够保证被引用的对象、它们的顺序以及列表的大小均不可从任何来源更改。而使用Collections.unmodifiableList方法则可能存在其他代码具有对底层列表的引用,尽管您拥有一个不可修改列表的引用,该代码仍然可以修改列表。如果您想实现真正的不可变性,则必须将列表填充为不可变对象。

@Vuntic,如果类是真正的不可变的,那么它的字段被声明为final,反射也无法改变它。 - Yishai
4
@Yishai,反射可以修改最终字段。试试看吧。 - amara
@Vuntic,使用setAccessible?这取决于安全管理器 ;)。 - Yishai
@Yishai... 是的,这确实取决于安全管理器。普通的安全管理器可以允许这样做。但是问题并没有涉及到安全管理器。而且(setAccessible)甚至不是修改final字段的方法。 - amara
5
@Vuntic,当我尝试修改一个final字段但没有设置setAccessable时,会出现异常。你有什么想法?顺便说一句,该问题并未涉及反射;) - Yishai
2
@Yishai 我知道我晚了7年才参与这个对话...但是为了公开记录,你可以通过使用反射来删除final修饰符来修改final字段。不过它只适用于对象,而不适用于基本类型。 - James Dunn

20

使用 Collections.unmodifiableList 可以创建一个围绕您的 List 的包装器。如果底层列表发生更改,则不可修改 List 的视图也会发生更改。

如文档所述,谷歌的代码会创建一个副本。这是一种更昂贵的计算方式,并消耗更多的内存,但是如果有人更改了原始列表,它也无法影响 ImmutableList。

这两者都不能防止您更改列表中的对象、其字段或字段的字段等。


3
正如文档所述,Google的代码会创建一个副本。它可能会创建一个副本。如果原始数据结构也是不可变的,它实际上可能会重用它。因此,如果您广泛使用ImmutableList,则会经常遇到这种情况,并且最终不需要复制太多内容。 - Luis Casillas

7

ImmutableList类似于Collections.unmodifiableList(new ArrayList(list))。请注意,新创建的ArrayList未分配给字段或变量。


3
  1. 不,包含的独立对象仍然可以被修改。集合只是存储所包含对象的引用,而不是每个对象的完整副本。
  2. 您可以通过修改您调用Collections.unmodifiableList(list)的父集合来修改列表。但是,是的,您可以使用setDimension来更改存储的列表元素。

0

您可能还想看一下 这个问题(Guava 中的 Collections.unmodifiableSet()ImmutableSet 有什么区别)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接