【问题标题】:Collections.unmodifiableList and defensive copyCollections.unmodifiableList 和防御性副本
【发布时间】:2013-02-27 11:35:09
【问题描述】:

如果我写

List<Integer> a1 = Arrays.asList(1, 2, 3);
List<Integer> a2 = Collections.unmodifiableList(a1);

a2 是只读的,但如果我写了

a1.set(0,10);

那么a2也被修改了。

如果在API中说:

返回指定集合的​​不可修改视图。这种方法 允许模块为用户提供对内部的“只读”访问 收藏。

那么,为什么如果我修改了原始集合,目标复制的集合也被修改了?

也许我误解了它的意思,如果是这样,有什么办法 为该集合写一份防御性副本?

【问题讨论】:

  • 如果您想要一个不可修改的 a1 版本,那么您可以克隆 a1 并将其设为不可修改的列表。那么对 a1 的更新不会影响 a2。

标签: java collections unmodifiable


【解决方案1】:

是的,您理解正确。这个想法是umodifiableCollection返回的对象不能直接改变,但可以通过其他方式改变(有效地通过直接改变内部集合)。

只要有东西可以访问内部列表,“不可修改”的集合就可以改变。

这就是为什么你通常构造一个不可修改的集合并确保没有任何东西可以进入内部列表:

Collection<Integer> myUmodifiableCollection = Collection.umodifiableCollection(Arrays.asList(1, 2, 3));

由于没有任何东西引用由asList 创建的List,因此这是一个真正不可修改的集合。

这种方法的优点是根本不需要复制原始的集合/列表,避免了使用内存和计算能力。

Guava 提供了ImmutableCollection 类(及其子类,例如ImmutableList),它提供了真正的不可变集合(通常通过复制源代码)。

【讨论】:

  • Arrays.asList() 已经返回一个固定大小的列表。那么为什么我们需要额外使用Collections.unmodifiableList() 来装饰它呢?我看到你和@assylias 都使用了相同的成语。你能解释一下吗?
  • @Geek:虽然Arras.asList() 返回一个固定大小的列表,但它不会返回一个不可修改的列表。您仍然可以使用set(int,T) 更改元素。 Collections.unmodifiableList() 通过禁止这一点来“修复”该问题。但在任何实际代码中,我可能只使用 Guava ImmutableList(链接到上面),因为它写起来更短,非常清晰,并且始终确保没有任何东西可以“泄漏”。
【解决方案2】:

从 Java 10 开始你可以做List.copyOf():

List<Integer> a1 = Arrays.asList(1, 2, 3);
List<Integer> a2 = List.copyOf(a1);

之后,对a1 的更改将无法通过a2 看到,因为List.copyOf() 会将您的列表内容复制到一个新列表中。副本不可修改。但请务必在使用前阅读该方法的文档,因为它不适用于包含 null 值的列表。

【讨论】:

    【解决方案3】:

    也许我误解了它的意思,如果是这样,那么编写该集合的防御性副本的方法是什么?

    通常,您会这样使用它:

    private List<Integer> a1 = Arrays.asList(1, 2, 3);
    
    public List<Integer> getUnmodifiable() {
        return Collections.unmodifiableList(a1);
    }
    

    调用getUnmodifiable 的人无权访问您的类的内部(即他们无法访问私有变量a1),将无法修改返回列表。

    【讨论】:

    • Arrays.asList() 已经返回一个固定大小的列表。那么为什么我们需要额外使用Collections.unmodifiableList() 来装饰它呢?我错过了什么?
    • 你不能添加到 a1 但你可以将任何项目设置为不同的东西。
    • @assylias 其实你可以修改列表中对象的内部,但你不能使用 set() 方法将旧对象替换为列表中的另一个对象。
    【解决方案4】:

    如果您希望保持 a1 可变并在没有 Guava 的情况下制作它的不可变副本,这是您可以做到的一种方法。

        List<Integer> a1 = Arrays.asList(1, 2, 3);
        List<Integer> a2 = Collections.unmodifiableList(new ArrayList<>(a1));
    

    【讨论】:

      【解决方案5】:

      这个想法是你不能通过a2修改列表。

      修改a1 列表确实会修改您在a2 中看到的内容——这是有意的。

      只是没有公开方式访问列表a1,你应该有你想要的:)

      【讨论】:

        【解决方案6】:

        API 说它内部集合在您的情况下,集合不是内部的

        关键是,当您在班级中有一个私人列表和该列表的 gettet 时,您可能希望 getter 的调用者在女巫情况下无法修改列表,您将不得不返回一个不可修改的列表.否则返回的列表只是对您的内部/私人列表的引用,因此可以修改其内容。

        【讨论】:

          【解决方案7】:

          声明:

          Collections.unmodifiableList(a1);
          

          返回原始集合的包装器,其修饰符方法抛出UnsupportedOperationException

          包装器是通读的,这意味着如果您修改 a1,更改会反映在包装的集合 a2 上。

          【讨论】:

          • 其实可以修改列表中对象的内部,但不能使用set()方法将旧对象替换为另一个对象。
          【解决方案8】:

          a1a2 将引用相同的数据(内存)。

          不可修改的部分来了,只有a2作为入口。

          如果您将a2 传递给您希望该方法具有幂等性的方法,则进行成像。这种情况a2有帮助。

          总而言之,您不能使用a2 指针修改数据。

          【讨论】:

            【解决方案9】:

            如果您需要不可修改且不可变的列表,或者换句话说,源列表的不可修改副本不依赖于其他库,请尝试以下操作:

            Collections.unmodifiableList(Collections.list(Collections.enumeration(sourceList)))
            

            Collections.list() 从其枚举中复制值。

            【讨论】:

              猜你喜欢
              • 2011-01-15
              • 1970-01-01
              • 2019-04-09
              • 2011-02-26
              • 1970-01-01
              • 2013-10-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多