不可变与不可修改的集合

时间:2012-01-17 09:39:20

标签: java collections immutability

来自Collections Framework Overview

  

不支持修改操作的集合(例如addremoveclear)称为 unmodifiable 。不可修改的集合是可修改的

     

另外保证Collection对象中的任何更改都不可见的集合称为 immutable 。不可变的集合是 mutable

我无法理解这种区别 unmodifiable immutable 之间有什么区别?

11 个答案:

答案 0 :(得分:186)

不可修改的集合通常是可修改集合的包装,其他代码可能仍然可以访问。因此,如果您只有对不可修改集合的引用, 无法对其进行任何更改,则不能依赖不更改的内容。

immutable 集合保证 nothing 不再能够更改集合。如果它包装了一个可修改的集合,它确保没有其他代码可以访问该可修改的集合。请注意,虽然没有代码可以更改集合包含引用的对象,但对象本身可能仍然是可变的 - 创建StringBuilder的不可变集合并不会以某种方式“冻结”这些对象。

基本上,区别在于其他代码是否能够改变背后的集合。

答案 1 :(得分:79)

基本上unModifiable集合是一个视图,所以间接它仍然可以从其他可修改的引用中“修改”。此外,它只是一个 只读视图 的另一个集合,当源集合更改时,unModifiable Collection将始终显示最新值。

但是,immutable集合可以被视为另一个集合的 只读副本 ,并且无法修改。在这种情况下,当源集合发生更改时,不可变集合不会反映更改

这是一个可视化这种差异的测试用例。

@Test
public void testList() {

    List<String> modifiableList = new ArrayList<String>();
    modifiableList.add("a");

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("--");


    //unModifiableList

    assertEquals(1, modifiableList.size());

    List<String> unModifiableList=Collections.unmodifiableList(
                                        modifiableList);

    modifiableList.add("b");

    boolean exceptionThrown=false;
    try {
        unModifiableList.add("b");
        fail("add supported for unModifiableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("unModifiableList.add() not supported");
    }
    assertTrue(exceptionThrown);

    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);

    assertEquals(2, modifiableList.size());
    assertEquals(2, unModifiableList.size());
            System.out.println("--");



            //immutableList


    List<String> immutableList=Collections.unmodifiableList(
                            new ArrayList<String>(modifiableList));

    modifiableList.add("c");

    exceptionThrown=false;
    try {
        immutableList.add("c");
        fail("add supported for immutableList!!");
    } catch (UnsupportedOperationException e) {
        exceptionThrown=true;
        System.out.println("immutableList.add() not supported");
    }
    assertTrue(exceptionThrown);


    System.out.println("modifiableList:"+modifiableList);
    System.out.println("unModifiableList:"+unModifiableList);
    System.out.println("immutableList:"+immutableList);
    System.out.println("--");

    assertEquals(3, modifiableList.size());
    assertEquals(3, unModifiableList.size());
    assertEquals(2, immutableList.size());

}

<强>输出

modifiableList:[a]
--
unModifiableList.add() not supported
modifiableList:[a, b]
unModifiableList:[a, b]
--
immutableList.add() not supported
modifiableList:[a, b, c]
unModifiableList:[a, b, c]
immutableList:[a, b]
--

答案 2 :(得分:11)

我认为主要区别在于可变集合的所有者可能希望提供对其他代码的集合的访问权限,但是通过不允许其他代码修改集合的接口提供该访问权限(同时将该功能保留给拥有的代码)。因此集合不是不可变的,但某些用户不允许更改集合。

Oracle的Java Collection Wrapper tutorial有这样的说法(强调补充):

  

不可修改的包装器有两个主要用途,如下所示:

     
      
  • 在构建集合后使集合不可变。在这种情况下,最好不要保留对支持的引用   采集。这绝对保证了不变性。
  •   
  • 允许某些客户端以只读方式访问您的数据结构。您保留对支持集合的引用,但是请参考   输出对包装器的引用。通过这种方式,客户可以看,但不是   修改,同时保持完全访问权限
  •   

答案 3 :(得分:2)

如果我们谈论JDK Unmodifiable* vs guava Immutable*,实际上差异也在于性能。如果不可变集合在常规集合周围包装(JDK实现是包装器),则它们可以更快且更节省内存。 Citing the guava team

JDK提供了Collections.unmodifiableXXX方法,但我们认为这些方法可以

&LT; ...&GT;

  • 效率低下:数据结构仍然具有可变集合的所有开销,包括并发修改检查,哈希表中的额外空间等。

答案 4 :(得分:2)

引用The Java™ Tutorials

  

与为包装集合添加功能的同步包装器不同,不可修改的包装器可以消除功能。特别是,它们通过拦截将修改集合并抛出UnsupportedOperationException 的所有操作来取消修改集合的能力。不可修改的包装器有两个主要用途,如下所示:

     
      
  • 在构建集合后使集合不可变。在这种情况下,最好不要维护对支持集合的引用。这绝对保证了不变性。

  •   
  • 允许某些客户端以只读方式访问您的数据结构。您保留对支持集合的引用,但分发对包装器的引用。通过这种方式,客户可以查看但不能修改,同时保持完全访问权限。

  •   

(强调我的)

这真的总结了。

答案 5 :(得分:2)

如果对象在构造后其状态无法更改,则该对象被认为是不可变的。在您创建集合的不可变实例后,只要对它的引用存在,它就会保存相同的数据。< /p>

不可变集合的一个优点是它是自动线程安全的。 包含不可变对象的集合在构造后自动是线程安全的。创建这样的集合后,可以将其交给多个线程,它们都会看到一致的视图。

但是,不可变对象集合与不可变对象集合不同。如果包含的元素是可变的,则这可能会导致集合行为不一致或使其内容出现改变。

简单来说,如果你给可变的东西添加一点不变性,你就会得到可变性。如果你给不可变的东西添加一点可变性,你就会得到可变性。

不可变和不可修改不一样:

不可变集合的行为方式与 Collections.unmodifiable... 包装器相同。然而,这些集合不是包装器——它们是由类实现的数据结构,任何修改数据的尝试都会导致抛出异常。

如果您创建一个 List 并将其传递给 Collections.unmodifiableList 方法,那么您将获得一个不可修改的视图。底层列表仍然是可修改的,对它的修改通过返回的 List 是可见的,所以它实际上不是不可变的。

要演示此行为,请创建一个 List 并将其传递给 Collections.unmodifiableList。如果您尝试直接添加到该不可修改的 List 中,则会引发 UnsupportedOperationException。

但是,如果你改变了原来的List,就不会产生错误,不可修改的List已经被修改了。

在这种情况下,要使集合在构建后不可变,最好不要维护对后备集合的引用。这绝对保证了不变性。

此外,允许某些客户端只读访问您的数据结构。您可以保留对后备集合的引用,但分发对包装器的引用。这样,客户可以查看但不能修改,而您保持完全访问权限。

因此,不可变集合可以包含可变对象,如果包含,则该集合既不是不可变的,也不是线程安全的。

答案 6 :(得分:1)

如上所述,unmodifiable与immutable不同,因为如果一个不可修改的集合有一个底层委托集合被一些其他对象引用并且该对象改变它,则可以改变不可修改的集合。

关于不可变,它甚至没有明确定义。但是,通常它意味着对象“不会改变”,但需要递归地定义。例如,我可以在类上定义不可变,其实例变量都是基元,并且其方法都不包含参数和返回基元。然后,这些方法递归地允许实例变量是不可变的,并且所有方法都包含不可变的参数并返回不可变的值。应保证方法随时间返回相同的值。

假设我们可以做到这一点,那么概念线程也是安全的。 你可能会被认为不可变(或随着时间的推移而变化)也意味着线程安全。 然而事实并非如此,这是我要做的主要观点这里还没有在其他答案中注意到。我可以构造一个永远返回相同结果但不是线程安全的不可变对象。为了看到这一点,假设我通过随时间保持添加和删除来构造不可变集合。现在,imm​​utable集合通过查看内部集合(可能随时间变化)返回其元素,然后(内部)添加和删除在创建集合后添加或删除的元素。显然,虽然集合总是返回相同的元素,但它不是线程安全的,仅仅因为它永远不会改变价值。

现在我们可以将immutable定义为线程安全且永远不会更改的对象。有一些指导用于创建通常会导致此类的不可变类,但请记住,可能存在创建不可变类的方法,这需要注意线程安全性,例如,如上面的“快照”集合示例中所述。

答案 7 :(得分:0)

Java™教程中的内容如下:

  

与同步包装不同,同步包装会向   包装的集合中,不可修改的包装会剥夺功能。   特别是,他们没有能力通过以下方式修改集合   拦截所有会修改集合的操作,并   引发UnsupportedOperationException。不可修改的包装器有   有两个主要用途,如下所示:

     

在集合建立后使其不可变。在这种情况下,   最好不要保留对支持的引用   采集。这绝对保证了不变性。

     

允许某些客户端对您的数据结构进行只读访问。您   保留对支持集合的引用,但分发对   包装器。这样,客户端可以查看但不能修改,而您   保持完全访问权限。

我认为它足以解释两者之间的区别。

答案 8 :(得分:0)

[Unmodifiable and Immutable]

无法更改的集合(对象)仍然可以通过更改原始对象来更改。可以使用参考。

Java提供了几种创建不可修改映射的方法:

  • Collections.unmodifiableMap()
  • Java 9 Map.of()Map.ofEntries()

答案 9 :(得分:0)

    // normal list
    List list1 = new ArrayList();
    list1.add(1);

    // unmodifiable list
    List list2 = Collections.unmodifiableList(list1);

    // immutable list
    List list3 = Collections.unmodifiableList(new ArrayList<>(list1));

    list1.add(2);
    list1.add(3);

    System.out.println(list1);
    System.out.println(list2);
    System.out.println(list3);

输出:

[1, 2, 3]
[1, 2, 3]
[1]

答案 10 :(得分:0)

不可修改与不可变集合

创建可修改的地图

Map<String, String> modifiableMap = new HashMap();
modifiableMap.put(“1”,”one”);
modifiableMap.put(“2”,”two”);
modifiableMap.put(“3”,”three”);

从 modifiableMap 创建一个 unmodifiableMap

 Map<String,String> unmodifiableMap = Collections.unmodifiableMap(modifiableMap);
    unmodifiableMap.put(“4”,”Four”)  ==>Exception
    modifiableMap.put(“4”,”Four”);   ==>Allowed, this will also reflect now in the unmodifiableMap , because unmodifiableMap() returns a wrapper around modifiableMap.
     

从 modifiableMap 创建一个 immutableMap

 Map<String,String> immutableMap = Collections.immutableMap(modifiableMap);
    immutableMap.put(“5”,”Five”) ==>Exception
    modifiableMap.put(“5”,”Five”);   ==>Allowed, BUT this will NOT reflect now in the immutableMap, because immutableMap() returns a copy of the modifiableMap.