番石榴的ImmutableXXX真的是不变的吗?

时间:2012-08-21 11:34:48

标签: java collections guava immutability

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

 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);
 }

所以,实际上没有复制,虽然我不知道如何在这种情况下实现泛型深层复制(可能是序列化?)。

那么..为什么他们称之为Immutable呢?

3 个答案:

答案 0 :(得分:28)

你在这里得到的是 immutable 深不可变的区别。

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

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

但是一个集合并不真正“拥有”它的元素。他们的州不被视为收藏国家的一部分;它是一种不同类型的类 - 容器。 (此外,正如您所说,它对使用的元素类型没有深入了解,因此无论如何都不知道如何复制元素。)

另一个答案指出,Guava集合应该使用术语 unmodifiable 。但是在集合的上下文中,术语不可修改不可变之间存在非常明确的区别,它与浅层和深层不变性无关。 “不可修改”说不能通过你的参考来改变这个实例; “不可变”意味着这个实例不能由你或任何其他演员改变。句号。

答案 1 :(得分:12)

列表本身是不可变的,因为您无法添加/删除元素。这些要素本身就不变性。更准确地说,我们有来自a historical Java 1.4.2 document的定义:

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

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

就防御性复制而言,请注意这是一个定义不明确的问题,并且Java中永远不会有一般的不可变集合会设法防御性地复制其元素。另外请注意,这样的集合比实际存在的不可变集合更不实用:当您将对象放入集合时,在99.99%的情况下,您希望该对象在那里,而不是一些甚至等于的其他对象。

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

    面对反思,
  • 没有什么是不可改变的。即使final字段也是可写的。
  • 甚至String,不可变性的基础,has been proven mutable在Java沙箱之外(没有SecurityManager - 覆盖了99%的真实Java程序。)

答案 2 :(得分:3)

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