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

通过查看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 ]
现在重点是为什么它指的是同一个对象? 为什么不创建新对象?

(底部的问题答案)

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

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

关键是,如文件中所述:

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

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

  • 给他一份你的List的深层副本,这样即使他修改了它,这也不会改变你的清单
  • 给他一个不可修改的集合 – 他不能修改它,你不需要创建一个新的集合。

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

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

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

从文档:

public static List unmodifiableList(List list)

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

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

Bozho 接受的答案是正确的。 这里有更多信息,示例代码和建议的替代方案。

unmodifiableList由原始列表支持

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

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

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

第四个词是关键: view 。 新列表对象不是新列表。 这是一个叠加。 就像在图纸上描绘纸张或透明胶片阻止您在图纸上制作标记一样,它也不会阻止您在下面修改原始图纸。

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

同上Collections.unmodifiableMapCollections.unmodifiableSet等。

这是另一个certificate问题的例子。

 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] 

谷歌番石榴

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

你可以列一个新的清单。

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

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

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

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

我找到了一种方法来做到这一点

 List unmodifiableList = Collections.unmodifiableList( new ArrayList(modifiableList)); List strings = new ArrayList(); // unmodifiable.add("New string"); strings.add("Aha 1"); strings.add("Aha 2"); List unmodifiable = Collections.unmodifiableList(strings); List 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); } 
  1. 你应该去创建列表的新对象,只有当原始对象将要被更改并且你需要备份时,当有人破坏它时,你可以用新的对象替换。

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