如果我们有原始列表,为什么我们可以更改不可修改的列表?

时间:2010-09-06 10:24:31

标签: java collections

通过查看Collections类的代码,我知道当我们使用方法unmodifiableList(List list)unmodifiableCollection(Collection c)时,它不是创建一个新对象,而是返回引用相同的对象并覆盖可以修改List [addaddallremoveretainAll ...]的方法 所以我跑了这个测试:

List modifiableList = new ArrayList();
modifiableList.add ( 1 );   
List unmodifiableList = Collections.unmodifiableList( modifiableList );
// unmodifiableList.add(3);  // it will throw the exception 
modifiableList.add ( 2 );       
System.out.println( unmodifiableList );

结果为[ 1,2 ] 现在重点是为什么它指的是同一个对象?为什么不创建新对象?

7 个答案:

答案 0 :(得分:11)

(底部的问题答案)

当您创建不可修改的列表时,其目的是不应由以外的人修改 - 即API的客户端。

方法unmodifiableList(..)创建一个类型为UnmodifiableList的新对象(但这不是公共类),它获取原始列表,并将所有方法委托给它除了修改它的方法。

关键是,正如文件中所述:

  

返回指定列表的不可修改视图。此方法允许模块为用户提供对内部列表的“只读”访问。

所以,举个例子:您有一个List个设备已经检测到并且可以运行,并且您希望为他们提供API的客户端。但他不应该改变他们。所以你有两个选择:

  • 给他一份List的深层副本,这样即使他修改了它,也不会改变你的列表
  • 给他一个不可修改的收藏 - 他不能修改它,你不用再创建一个新系列了。

现在,问题的标题就是答案 - 不可修改的列表是原始集合的视图。因此,如果您需要向其中添加新项目 - 比如说,您发现了一个刚刚插入的新设备,客户端将能够在不可修改的视图中看到它

答案 1 :(得分:3)

  

现在重点是它所指的原因   对同一个对象?为什么不呢   创建一个新对象?

性能。它只是不缩放以制作完整副本。制作完整副本将是一个线性时间操作,这显然是不切实际的。此外,正如其他人已经指出的那样,关键是您可以传递不可修改列表的引用,而不必担心它会被更改。这对多线程程序非常有用。

答案 2 :(得分:2)

来自文档:

  

public static List unmodifiableList(List list)

     

返回指定列表的不可修改的视图。此方法允许模块为用户提供“只读”访问内部列表。对返回列表的查询操作“读取”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致UnsupportedOperationException。

答案 3 :(得分:0)

我相信秘密在于实现细节...... Collection.unmodifiableList()将简单地为您提供可修改的可修改列表。我的意思是不可修改的列表包含对内部可修改列表的引用。

答案 4 :(得分:0)

The accepted Answer之前的{p> Bozho是正确的。这里有更多信息,示例代码和建议的替代方案。

unmodifiableList由原始列表支持

unmodifiableList实用程序类中的Collections方法不会创建新列表,而是由原始列表创建伪列表后备。通过“不可修改”对象进行的任何添加或删除尝试都将被阻止,因此名称符合其目的。但实际上,正如您所示,原始列表可以被修改,同时影响我们的次要非完全不可修改的列表。

这在课程文档中有详细说明:

  

返回指定列表的不可修改视图。此方法允许模块为用户提供对内部列表的“只读”访问。对返回列表的查询操作“读取”到指定列表,并尝试修改返回的列表,无论是直接还是通过其迭代器,都会导致UnsupportedOperationException。

第四个词是关键:view。新列表对象不是新列表。这是一个叠加。就像绘图上的tracing papertransparency film阻止您在图纸上制作标记一样,它也不会阻止您在下方修改原始图形。

故事的道德:不要使用Collections.unmodifiableList来制作列表的防御性副本。

同样适用于Collections.unmodifiableMapCollections.unmodifiableSet,等等。

这是另一个证明问题的例子。

String dog = "dog";
String cat = "cat";
String bird = "bird";

List< String > originalList = new ArrayList<>( 3 );
originalList.add( dog );
originalList.add( cat );
originalList.add( bird );

List< String > unmodList = Collections.unmodifiableList( originalList );
System.out.println( "unmod before: " + unmodList );  // Yields [dog, cat, bird]
originalList.remove( cat );  // Removing element from original list affects the unmodifiable list?
System.out.println( "unmod after: " + unmodList );  // Yields [dog, bird]

Google Guava

对于防御性编程而不是Collections类,我建议使用Google Guava库及其ImmutableCollections工具。

您可以制作新的清单。

public static final ImmutableList<String> ANIMALS = ImmutableList.of(
        dog,
        cat,
        bird );

或者您可以制作现有列表的防御副本。在这种情况下,您将获得一个新的单独列表。从原始列表中删除影响(缩小)不可变列表。

ImmutableList<String> ANIMALS = ImmutableList.copyOf( originalList ); // defensive copy!

但请记住,虽然集合自己的定义是独立的,但是包含的对象由原始列表和新的不可变列表共享。在制作防御性副本时,我们不会复制“狗”对象。只有一个狗对象保留在内存中,两个列表都包含指向同一只狗的引用。如果修改了“dog”对象中的属性,则两个集合都指向同一个dog对象,因此两个集合都将看到dog的新属性值。

答案 5 :(得分:0)

我发现一种方法是

List unmodifiableList = Collections.unmodifiableList( new ArrayList(modifiableList));

List<String> strings = new ArrayList<String>();
        // unmodifiable.add("New string");
        strings.add("Aha 1");
        strings.add("Aha 2");
        List<String> unmodifiable = Collections.unmodifiableList(strings);
        List<String> immutableList = Collections.unmodifiableList(new ArrayList<>(strings));
        // Need some way to fix it so that Strings does not Modify
        strings.add("Aha 3");
        strings.add("Aha 4");

        strings.remove(0);

        for (String str : unmodifiable) {
            System.out.println("Reference Modified :::" + str);
        }

        for (String str : immutableList) {
            System.out.println("Reference Modified :::" + str);
        }

答案 6 :(得分:-2)

  1. 你应该去创建列表的新对象,只有当原始对象要改变并且你需要备份时,当有人破坏它时,你可以用新的对象替换。

  2. 要创建一个ummodifiable对象,我将包装原始对象并阻止添加,通过抛出异常删除。但是你知道,我可以改变列表中的每个对象。就像你在umodifiable列表中有一个person对象一样,我仍然可以在列表中更改person对象的名称。