【问题标题】:Java Immutable CollectionsJava 不可变集合
【发布时间】:2011-12-04 12:35:05
【问题描述】:

来自Java 1.6 Collection Framework documentation

不支持任何修改操作的集合(例如addremoveclear)被称为不可修改。 [...] 额外保证 Collection 对象中没有任何变化的集合被称为不可变

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

第二个问题:
对于不可变的集合,如何提供指定的额外保证?如果集合中元素的状态由线程更新,那么状态中的这些更新在持有不可变集合的线程上不可见就足以实现不变性吗?

要使集合不可变,如何提供指定的额外保证?

【问题讨论】:

  • 一般来说(特别是在函数式语言中),不可变(又名持久)集合可能会在某种意义上发生变化,您可以获得该集合的新状态,但同时状态仍可从其他链接获得。例如,newCol = oldCol.add("element") 将生成新集合,它是旧集合的副本,多了 1 个元素,并且对 oldCol 的所有引用仍将指向相同的未更改的旧集合。

标签: java collections immutability


【解决方案1】:

在 Java 9 之前,Collections.unmodifiableXXX() 方法用于创建不可修改的集合。这些方法的行为类似于包装器方法,它们返回原始集合的不可修改视图或只读视图。即您不能通过这些包装方法返回的引用执行修改操作,如添加、删除、替换、清除等。但是,如果您有其他对原始集合的引用,则可以修改原始集合,这些修改将反映在这些方法返回的视图中。

例如,

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class Java9ImmutableCollections 
{
    public static void main(String[] args) 
    {
        List<String> sportList = new ArrayList<String>();
         
        sportList.add("Hockey");
        sportList.add("Cricket");
        sportList.add("Tennis");
         
        List<String> unModifiableSportList = Collections.unmodifiableList(sportList);
 
        System.out.println(sportList);    //Output : [Hockey, Cricket, Tennis]
         
        System.out.println(unModifiableSportList);    //Output : [Hockey, Cricket, Tennis]
         
        unModifiableSportList.add("Wrestling");     //It gives run-time error
         
        sportList.add("Kabaddi");      //It gives no error and will be reflected in unModifiableSportList
         
        System.out.println(sportList);    //Output : [Hockey, Cricket, Tennis, Kabaddi]
         
        System.out.println(unModifiableSportList);    //Output : [Hockey, Cricket, Tennis, Kabaddi]
         
    }
}

从 Java 9 开始,引入了静态工厂方法来创建不可变集合。

1) Immutable List : List.of()
2) Immutable Set : Set.of()
3) Immutable Map : Map.of() or Map.ofEntries()

不可变与不可修改:

由 Collections.unmodifiableXXX() 包装方法返回的 Java 9 不可变集合和不可修改集合是不同的。不可修改的集合只是原始集合的只读视图。您可以对原始集合执行修改操作,这些修改将反映在这些方法返回的集合中。但是,Java 9 静态工厂方法返回的不可变集合是 100% 不可变的。它们一经创建就无法修改。

来源:https://javaconceptoftheday.com/java-9-immutable-collections/

【讨论】:

    【解决方案2】:

    现在 java 9 具有不可变 List、Set、Map 和 Map.Entry 的工厂方法。

    在 Java SE 8 及更早的版本中,我们可以使用 Collections 类的实用方法,如 unmodifiableXXX 来创建不可变的 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");
    

    【讨论】:

    • 在 Java 10 中添加了 List.copyOfSet.copyOf,它们允许创建列表/集合的不可修改副本,或者如果它已经不可修改,则返回给定集合,请参阅 JDK-8191517
    【解决方案3】:
    Collection<String> c1 = new ArrayList<String>();
    c1.add("foo");
    Collection<String> c2 = Collections.unmodifiableList(c1);
    

    c1可变(即既不不可修改也不不可变)。
    c2不可修改 em>:它本身无法更改,但如果稍后我更改c1,那么那个更改将在c2 中可见。

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

    关于你的第二个问题:

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

    【讨论】:

    • @John:不,不是。至少不是根据 OP 引用的定义。
    • @JohnVint,真的吗?我不这么认为,因为使用 c1 ,我仍然可以向其中添加元素,并且这种添加对 c2 是可见的。这不意味着它不是一成不变的吗?
    • 好吧,我猜如果 c1 可以逃脱,那么它就不是不可变的,但不可变性也指集合中的内容。我更多的是指 java.util.Date 的 unmodifiableCollection
    • @Vint:“如果c1 不转义”不是规范中任何地方都区分的考虑因素。在我看来,如果可以以使其不可变的方式使用集合,那么您应该始终将其视为不可变.
    • 让我引用定义:“另外保证 ...的集合”(强调我的)。 Collection.unmodifiableList() 不能保证这一点,因为它不能保证它的论点不会逃逸。 Guava ImmutableList.of 总是会产生一个不可变的List,即使你让它的参数逃逸。
    【解决方案4】:

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

    不可变集合根本无法更改 - 它们不包装另一个集合 - 它们有自己的元素。

    这是来自 guava 的 ImmutableList 的引述

    Collections.unmodifiableList(java.util.List&lt;? extends T&gt;) 不同,Collections.unmodifiableList(java.util.List&lt;? extends T&gt;) 是一个仍然可以更改的单独集合的视图,ImmutableList 的实例包含自己的私有数据并且永远不会更改。

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

    【讨论】:

    • @Bhaskar - 见我的最后一段。
    • 如果封装在另一个不可修改集合中的集合没有任何其他对它的引用,那么不可修改集合的行为是否与不可变集合完全相同?
    • @Bozho,如果没有提供对支持集合的引用,并且只提供了不可修改的集合包装器引用,那么您无法更改它。那你为什么说“你不能确定”?它是否指出某个线程或以某种方式因为支持集合是可修改的而被修改的场景?
    • @AKS:当集合包含在 unmodifiableList 中时,仅接收对该列表的引用的代码将无法修改它,但任何引用原始列表的代码并且可以在创建包装器之前对其进行修改,之后仍然可以这样做。如果创建原始列表的代码知道它曾经存在的每个引用发生了什么,并且知道它们中的任何一个都不会落入可能修改列表的代码手中,那么它就可以知道列表永远不会修改的。但是,如果引用是从外部代码接收到的...
    • ...unmodifiableList 以及任何使用它的代码都无法知道包装的集合是否或如何更改。
    【解决方案5】:

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

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

    其次,它提供来自 Clojure 的持久性集合(添加了泛型)并确保添加到集合中的元素是不可变的。这些的表现显然相当不错。集合都是不可变的,但实现了 Java 集合接口(和泛型)以供检查。 Mutation 返回新的集合。

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

    【讨论】:

      【解决方案6】:

      我相信这里的重点是,即使集合是不可修改的,也不能确保它不会改变。以一个集合为例,如果元素太旧,它会驱逐元素。不可修改只是意味着持有引用的对象不能改变它,而不是它不能改变。一个真实的例子是Collections.unmodifiableList 方法。它返回列表的不可修改视图。传递给此方法的 List 引用仍然是可修改的,因此该列表可以被传递的引用的任何持有者修改。这可能会导致 ConcurrentModificationExceptions 和其他不好的事情。

      不可变,意味着集合绝对不能改变。

      第二个问题:一个不可变集合并不意味着集合中包含的对象不会改变,只是该集合在它所拥有的对象的数量和组成上不会改变。换句话说,集合的引用列表不会改变。这并不意味着被引用对象的内部不能改变。

      【讨论】:

      • 好一个!因此,如果我在 Unmodifiable Collection 上创建一个包装器并将可修改的集合引用设为私有,那么我可以确定它是不可变的。对吗?
      • 如果我理解你的问题,那么答案是否定的。包装 Unmodifiable 并不能保护它不被传递给 unmodifiableList 的集合被修改。如果你想这样做,请使用ImmutableList
      【解决方案7】:

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

      例如

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

      【讨论】:

      • 如果不使用原始集合引用来修改基础集合,那么有哪些各种原因(如果有)会导致无法修改的集合发生变化?
      • 糟糕。我完全讨厌这个。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-19
      • 2011-03-11
      • 1970-01-01
      • 2012-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多