是否有充分的理由将 OpenJDK 代码中的以下 ArrayList 构造函数分为两种不同的情况?

问题描述 投票:0回答:1

例如在 JDK17 中阅读 ArrayList 类的 OpenJDK 代码时

https://github.com/openjdk/jdk17/blob/master/src/java.base/share/classes/java/util/ArrayList.java

我偶然发现了以下构造函数:

 public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

区分

c.getClass()
是否是
ArrayList.class
的原因是什么?这种情况有必要分拆吗?

(我只是想了解 Java 代码,它是 ArrayList 类的 OpenJDK 发行版的一部分。)

java arraylist reflection constructor java-17
1个回答
0
投票

免责声明:我没有参与编写这段代码,也不知道有谁参与过


您正在查看的构造函数是“复制构造函数”;它的目的是创建给定集合的浅表副本

c
。这意味着
c
的元素必须复制到新集合中,但新集合的“结构”(
ArrayList
中的数组)必须不同。否则,对
c
的结构修改也将通过新集合可见,反之亦然。

这部分:

if (c.getClass() == ArrayList.class) {
    elementData = a;
} else {
    elementData = Arrays.copyOf(a, size, Object[].class);
}

确保新的

ArrayList
c
不共享同一个数组。但是
ArrayList
knows 它自己的
toArray()
实现返回一个不同的数组。所以,当
c
ArrayList
时,它避免了复制数组,从而避免了不必要的工作。否则,如果
c
是任何其他集合类型的实例,包括
ArrayList
的子类型(可以覆盖
toArray()
),那么就不能再依赖
toArray()
的知识,因此防御性的已制作数组的副本。

也就是说,

Collection#toArray()
的合同规定:

返回的数组将是“安全的”,因为该集合不维护对其的引用。 (换句话说,即使此集合由数组支持,此方法也必须分配一个新数组)。因此,调用者可以自由修改返回的数组。

因此,复制数组似乎是没有必要的,使得上面的代码显得多余。但我可以想到应该复制数组的两个原因:

  1. 另一种优化。

    Collection#toArray()
    的文档中没有提及任何有关返回数组的 length 的内容。这意味着返回的数组的容量可能大于元素的数量。上述代码进行的复制可确保数组的容量等于 c 的大小,从而可能节省内存。
    
    

  2. 安全。恶意实现
  3. Collection

    可能会破坏

    toArray()
    的契约并保留对数组的引用,从而允许参与者意外地修改新的
    ArrayList
    。由于
    ArrayList
    是一个使用非常广泛的类,因此防范此类事情非常重要。
    
    

  4. 然而,在这两种情况下,
ArrayList

仍然

知道
,它自己的toArray()实现返回一个容量等于元素数量的不同数组,因此在
c.getClass() == ArrayList.class
时仍然可以避免复制数组。
    

© www.soinside.com 2019 - 2024. All rights reserved.