【问题标题】:Are the Java 10 copyOf factory methods suitable for defensive copies in constructorsJava 10 copyOf 工厂方法是否适合构造函数中的防御性副本
【发布时间】:2021-04-29 13:32:47
【问题描述】:

当实现一个 不可变 类时,该类有一个由Collection 初始化的聚合 给构造函数,构造函数必须制作Collection 的防御性副本,以防止调用者随后改变聚合。

在 Java 10 之前,这将导致如下代码:

class Thing
{
   private final List<Widget> widgets;

   public Thing(List<Widget> widgets) {
      this.widgets = Collections.unmodifiableList​(new ArrayList<>(widgets));
   }

   public List<Widget> getWidgets() {
      return widgets;
   }
}

Java 10 添加了List.copyOf() 和朋友,它们创建了给定Collection 的不可修改副本。这表明对于 Java 10+,我们可以改为编写如下代码:

class Thing
{
   private final List<Widget> widgets;

   public Thing(List<Widget> widgets) {
      this.widgets = List.copyOf(widgets);
   }

   public List<Widget> getWidgets() {
      return widgets;
   }
}

我说的对吗?

【问题讨论】:

    标签: java collections copy immutability


    【解决方案1】:

    设计意图List.copyOf 和其他copyOf 方法的实现完全按照你的意愿去做,它应该适合制作防御性副本。这个想法是List.copyOf 总是复制,除非它可以证明不复制是安全的。

    来自List.copyOf specification 的其他上下文是:

    返回一个unmodifiable List,其中包含给定集合的元素,按其迭代顺序。给定的 Collection 不能为 null,并且不能包含任何 null 元素。如果随后修改了给定的 Collection,则返回的 List 将不会反映此类修改。

    实施说明: 如果给定的 Collection 是unmodifiable List,则调用 copyOf 通常不会创建副本。

    关键断言是第一段中的最后一句,它表示如果参数随后被修改,则该修改不会出现在返回的列表中。也就是说,返回的列表在概念上始终是一个副本。

    另请注意,“不可修改的列表”是指向列表规范的特定部分的链接,其中描述了由List.ofList.copyOf 和流的Collectors.toUnmodifiableList 返回的列表实例。

    Collections.unmodifiableList 返回的实例不是此定义中的“不可修改列表”。相反,它是unmodifiable view。如果一个对支持集合的引用,并且如果该集合本身是可修改的,则此类视图明确允许修改支持集合。

    是的,“不可修改列表”和“不可修改视图”之间的区别令人困惑,但我们已经确定了这一点。对不起。有一个替代方案是“不可变的”。但这是不准确的,因为这些实例实际上是可变的,因此我们避免在这些情况下使用“不可变”。

    无论如何,实际行为反映了这个规范:

    var list1 = new ArrayList<String>(Arrays.asList("a", "b", "c"))
    var list2 = Collections.unmodifiableList(list1)
    var list3 = List.copyOf(list2)
    var list4 = List.copyOf(list3)
    list2 == list3
    ==> false
    list3 == list4
    ==> true
    list1.add("d")
    list2
    ==> [a, b, c, d]
    list3
    ==> [a, b, c]
    

    换句话说,List.copyOf确实制作了不可修改视图的防御性副本,而这种行为是规范所要求的。

    【讨论】:

      【解决方案2】:

      您可以安全地使用List.copyOf 来制作防御性副本——至少在 JDK16 上是这样。 List.copyOf 的代码在 ImmutableCollections 中调用此方法,该方法在特定情况下返回相同的 List - 当列表已经是 List.of 之前生成的 List 的实例时:

      static <E> List<E> listCopy(Collection<? extends E> coll) {
          if (coll instanceof List12 || (coll instanceof ListN && ! ((ListN<?>)coll).allowNulls)) {
              return (List<E>)coll;
          } else {
              return (List<E>)List.of(coll.toArray()); // implicit nullcheck of coll
          }
      }
      

      Collections.unmodifiableList 的调用会返回其他类型的列表,这些列表不会在上面List.copyOf 中的if 中作为“不可修改的列表”传递,因此它们将始终被List.copyOf 复制:

      public static <T> List<T> unmodifiableList(List<? extends T> list) {
          return (list instanceof RandomAccess ?
                  new UnmodifiableRandomAccessList<>(list) :
                  new UnmodifiableList<>(list));
      }
      

      查看此测试程序的输出 - List.copyOf 对传入的参数进行适当的新副本,这些参数源自 ArrayListCollections.unmodifiableList,但如果 List 是由 List.of/copyOf 方法生成的,则不会创建副本:

      public static void main(String[] args) {
          var a = new ArrayList<String>();
          a.add("ONE");
          var b = Collections.unmodifiableList(a);
          a.add("TWO");
          var c = List.copyOf(b);
          a.add("THREE");
          var d = List.copyOf(c);
          System.out.println("a="+a);
          System.out.println("b="+b);
          System.out.println("c="+c);
          System.out.println("d="+d);
          System.out.println("a == b "+(a == b));
          System.out.println("b == c "+(b == c));
          System.out.println("c == d "+(c == d));
      }
      

      打印:

      a=[ONE, TWO, THREE]
      b=[ONE, TWO, THREE]
      c=[ONE, TWO]
      d=[ONE, TWO]
      a == b false
      b == c false
      c == d true
      

      我不知道上面是否会在 JDK10..15 中产生不同的结果,因为我没有安装这些。

      【讨论】:

        猜你喜欢
        • 2016-05-22
        • 1970-01-01
        • 2011-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-12
        • 1970-01-01
        相关资源
        最近更新 更多