不可变与不可修改的集合


170

集合框架概述中

类别不支持修改操作(如addremoveclear)被称为不可修改的。不可修改的集合是可修改的

额外保证Collection对象的任何变化都不可见的集合称为不可变的。不变的集合是可变的

我不明白区别。这里不可修改不可变
之间有什么区别?

Answers:


207

不可修改的集合通常是可修改集合的包装,其他代码仍可以访问该可修改集合。所以,虽然不能做任何更改,如果你只需要不可修改的集合的引用,你不能依靠内容不改变。

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

基本上,区别在于其他代码是否可以更改背后的集合。


63
不可变的集合不能保证没有任何改变。它只是确保了集合本身不能被更改(不是通过包装,而是通过复制)。集合中存在的对象仍然可以更改,并且对此不做任何保证。
Hiery Nomus 2012年

8
@HieryNomus:请注意,我没有说什么都不能改变-我说没有什么可以改变集合。
乔恩·斯基特

1
好的,可能是误读了;)但是尽管澄清一下还是很好的。
Hiery Nomus 2012年

5
所以,你在说什么。为了实现真正的不变性,您需要一个不可变的集合,其中包含不可变类型的项。
Evan Plaice

1
@savanibharat:这取决于是否还有任何代码路径可以修改list。如果以后可以调用某些东西,list.add(10)那么coll它将反映出这种变化,所以不,我不会将其称为不变的。
乔恩·斯基特

92

基本上unModifiableCollection是一个视图,因此可以间接地从其他可修改的引用中对其进行“修改”。同样,因为它只是另一个集合的只读视图,所以当源集合发生更改时,不可修改的集合将始终显示最新值。

但是,immutable可以将Collection视为另一个Collection的只读副本,并且不能进行修改。在这种情况下,当源集合更改时,不可变的集合不会反映更改

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

@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]
--

我看不到任何区别,您能否指出不可变之处有何不同?我可以看到Immutable和Unmodifiable都抛出错误,并且不支持add。我在这里想念什么吗?
AKS

2
@AKS请在将“ c”添加到列表后查看最后三个列表条目的输出,同时大小modifiableListunModifiableList增加的immutableList大小均未更改
Prashant Bhate

1
哦! 得到它了!:) ..因此,您在这里使用了modifiableList中的更改来修改了unmodifableList,但是ImmutableList无法修改。但是同样可以修改ImmutableList的方式也一样,我认为这里的客户端将只能访问ImmutableList引用,而使用ImmutableList创建的对modifiableList的引用将不会暴露给客户端。对?
AKS

1
是的,因为没有对new ArrayList<String>(modifiableList)immutableList的引用不能被修改
Prashant Bhate

@PrashantBhate嗨,new ArrayList<String>(modifiableList)因为没有引用new?谢谢。
Unheilig 2014年

11

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

Oracle的Java Collection Wrapper教程说了这句话(添加了重点):

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

  • 使集合一旦建立便不可变。在这种情况下,最好不要保留对后备集合的引用。这绝对保证了不变性。
  • 允许某些客户端以只读方式访问您的数据结构。您保留对后备集合的引用,但分发对包装器的引用。这样,在您保持完全访问权限的同时,客户端可以查看但不能修改

3

如果我们谈论的是JDK Unmodifiable*与番石榴Immutable*,实际上区别也在于性能。如果不可变集合不是常规集合的包装器(JDK实现是包装器),则它们既可以更快也可以提高内存效率。 引用番石榴队

JDK提供了Collections.unmodifiableXXX方法,但在我们看来,这些方法可以是

<...>

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

考虑到性能,您还应该考虑到不可修改的包装器不会复制该集合,就像在guava中使用的不可变版本一样,现在在jdk9 +中使用的不可变版本也List.of(...)确实复制了两次!
benez

2

引用Java™教程

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

  • 使集合一旦建立便不可变。在这种情况下,最好不要保留对后备集合的引用。这绝对保证了不变性。

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

(强调我的)

这真的总结了一下。


1

如上所述,不可修改不像是不可更改的,因为例如,如果不可修改的集合具有由其他对象引用的基础委托集合,并且该对象对其进行了更改,那么不可修改的集合可以被更改。

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

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

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


1

Java™教程说:

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

使集合一旦建立便不可变。在这种情况下,最好不要保留对后备集合的引用。这绝对保证了不变性。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.