java - 在测试 guava ImmutableList 行为时感到困惑
问题描述
public class TestImmutableCollection {
static class Helper {
int val;
public Helper(int val) {
this.val = val;
}
@Override
public String toString() {
return String.valueOf(val);
}
}
public static void main(String[] args) {
List<Helper> origin = new ArrayList<>();
origin.add(new Helper(10));
origin.add(new Helper(11));
origin.add(new Helper(13));
ImmutableList<Helper> ul = ImmutableList.copyOf(origin);
ul.get(0).val = 15;
System.out.println(ul);
System.out.println(origin);
}
}
在之前的一次采访中,有人问我关于不可变性的问题,因此我在 Internet 上搜索了 Java 中的不可变集合。所以我遇到了这篇Java Immutable Collections帖子,其中很多人提到 Guava 对 Immutable Collections 有更好、更安全的实现。在使用 guava 之前,我已经使用 JDK 的内置 UnmodifiableList 测试了上面的代码,结果 UnmodifiableList 只是原始列表的包装器,因此如果我使用 get 访问内部元素,两个列表的内容都会更新,然后更新元素对象的字段。
正如人们在上一篇文章中所说:
与 Collections.unmodifiableList(java.util.List<? extends T>) 不同,后者是仍然可以更改的单独集合的视图,ImmutableList 的实例包含自己的私有数据并且永远不会更改。
然后我用 guava ImmutableList 测试代码。但它仍然给出了相同的结果。创建的 ImmutableListcopyOf()
和原始列表的内容都发生了变化。
我很困惑为什么会这样。我在这里理解不变性的范围吗?Collection中元素内容的变化不会在这里判断为Collection的变化吗?但是番石榴的文档说得很绝对,它永远不会改变。
如果是这样,那么在这种情况下,guava ImmutableList 和 JDK 的 UnmodifiableList 有什么区别?
希望有人可以对此有所启发。感谢。
更新:嗯,我知道不对类的字段添加任何访问约束不是一个好的设计。但是试着想象一个现实的例子,其中 Helper 可以像用户的帐户,字段可以是用户的username,您肯定会提供更新此 username 字段的方法,对吧?那么在这种情况下,我怎样才能在列表中显示帐户信息而不让调用者修改此列表中元素的内容?
解决方案
既不UnmodifiableList
也不ImmutableList
保证存储在这些集合中的元素永远不会改变。集合本身是不可变的,而不是存储在其中的元素。这些集合必须返回存储元素的副本,但它们不会。您不能向这些集合添加/删除元素,但您仍然可以修改元素本身。
Javadocs的引用对ImmutableCollection
这里有很大帮助:
浅的不变性。永远不能在此集合中添加、删除或替换元素。这是比 更强大的保证,
Collections.unmodifiableCollection(java.util.Collection<? extends T>)
无论何时修改包装的集合,其内容都会更改。
基本上UnmodifiableList
只是其他一些集合的包装。如果您以某种方式获得了该包装的集合,则可以对其进行修改(添加或删除元素),并且这些更改将反映在其UnmodifiableList
本身中。
ImmutableList
另一方面,复制对原始集合的所有元素的引用并且不再使用它。因此,新创建ImmutableList
的集合和原始集合是分开的,您可以修改原始集合(添加或删除元素)并且不会在ImmutableList
.
以及来自Javadocs的额外引用ImmutableCollection
:
警告:与任何集合一样,修改包含在集合中的元素(以影响其
Object.equals(java.lang.Object)
行为的方式)几乎总是一个坏主意。将导致未定义的行为和错误。通常最好避免使用可变对象作为元素,因为许多用户可能希望您的“不可变”对象是深度不可变的。
编辑以回答您添加的问题:
如果您希望调用者能够对这些对象执行任何操作,但不希望这些更改影响您的原始数据,您可以进行深度复制 - 复制集合中的每个元素。
其他方法是为Account
该类编写一个包装器,访问受限 - 没有任何设置器。通过简单的组合,您可以阻止所有修改。