8

我使用番石榴已经有一段时间了,并且非常信任它,直到昨天我偶然发现了一个例子,这让我开始思考。长话短说,这里是:

 public static void testGuavaImmutability(){
     StringBuilder stringBuilder = new StringBuilder("partOne");
     ImmutableList<StringBuilder> myList = ImmutableList.of(stringBuilder);
     System.out.println(myList.get(0));
     stringBuilder.append("appended");
     System.out.println(myList.get(0));
 }

运行后,您可以看到ImmutableList中条目的值已更改。如果这里涉及两个线程,一个可能碰巧看不到另一个的更新。

另外,让我非常不耐烦的事情是 Effective Java 中的 Item15,第五点是这样说的:

在构造函数中制作防御副本——这看起来很合逻辑。

查看 ImmutableList 的源代码,我看到:

 SingletonImmutableList(E element) {
     this.element = checkNotNull(element);
 }

因此,实际上没有复制,尽管我不知道在这种情况下如何实现通用深度复制(可能是序列化?)。

那么..为什么它们被称为不可变呢?

4

3 回答 3

28

你在这里得到的是不可变深度不可变之间的区别。

不可变对象永远不会改变,但它所指的任何东西都可能改变。深度不变性要强得多:基础对象和您可以从中导航到任何对象都不会改变。

每个都适合自己的情况。当您创建自己的具有 type 字段的类Date时,该日期您的对象所有;它确实是其中的一部分。因此,您应该制作它的防御性副本(在进出路上!)以提供深度不变性。

但是一个集合并不真正“拥有”它的元素。它们的状态不被视为集合状态的一部分;它是一种不同类型的类——一个容器。(此外,正如您所提到的,它对正在使用的元素类型没有深入的了解,因此它也不知道如何复制元素。)

另一个答案指出 Guava 集合应该使用术语unmodifiable但是在集合的上下文中,术语不可修改不可变之间有一个非常明确的区别,它与浅不可变与深不可变无关。“不可修改”表示无法通过您拥有的参考来更改此实例;“不可变”是指此实例不能更改,无论是您还是任何其他参与者。

于 2012-08-21T14:08:22.387 回答
12

列表本身是不可变的,因为您无法添加/删除元素。关于不变性,这些元素是独立的。更准确地说,我们有来自历史 Java 1.4.2 文档的定义:

  • 不支持任何修改操作(如添加、删除和清除)的集合称为不可修改的。非不可修改的集合称为可修改的。
  • 额外保证 Collection 对象中的任何更改都不可见的集合称为不可变集合。不可变的集合称为可变集合。

请注意,为了使这些定义有意义,我们必须假设抽象意义上的集合和表示该集合的对象之间存在隐含的区别。这很重要,因为根据该术语的任何标准定义,表示不可变集合的对象本身并不是不可变的。例如,它的equals关系没有时间一致性,这是对不可变对象的重要要求。

至于防御性复制,请注意这通常是一个定义不明确的问题,Java 中永远不会有一个通用的不可变集合能够防御性地复制其元素。另外请注意,这样的集合将不如真正存在的不可变集合有用:当您将对象放入集合中时,在 99.99% 的情况下,您希望该对象存在,而不是其他一些甚至不等于它。

对象不变性(与集合不变性相反)有一个非常标准的定义,它假设从不可变对象可到达的整个对象图的传递不变性。然而,从字面上看,这样的定义在现实世界中几乎永远不会得到满足。有两个案例:

  • 在反思面前,没有什么是一成不变的。甚至final字段也是可写的。
  • 甚至String,那个不变性的堡垒,已经被证明在 Java 沙箱之外是可变的(没有SecurityManager- 它涵盖了 99% 的真实世界 Java 程序)。
于 2012-08-21T11:37:49.070 回答
3

你混合了列表的不变性和它包含的对象的不变性。在不可变集合中,您不能添加/删除对象,但如果它包含的对象是可变的,您可以在get()ing 之后修改它们。

于 2012-08-21T11:40:55.977 回答