假设我有这个代码:
String[] left = { "1", "2" };
String[] leftNew = Collections.emptyList().toArray(left);
System.out.println(Arrays.toString(leftNew));
这将打印出来 [null, 2]
。这个 有点 有道理,因为我们有一个空列表,它在某种程度上假设我们正在传递一个更大的数组并将第一个元素设置为null的事实。这可能是说第一个元素不存在于空列表中,因此设置为 null
。
但这仍然令人困惑,因为我们传递一个特定类型的数组只是为了帮助推断出类型 回 阵列;但无论如何,这是至少具有某种逻辑的东西。但是,如果我这样做:
String[] right = { "nonA", "b", "c" };
// or Collections.singletonList("a");
// or a plain List or Set; does not matter
String[] rightNew = Collections.singleton("a").toArray(right);
System.out.println(Arrays.toString(rightNew));
以上一个例子作为参考,我希望这个例子能够显示:
["a", "b", "c"]
但是,对我来说有点不合预期,它打印:
[a, null, c]
当然,我转到明确说明这是预期的文档:
如果此集合适合指定的数组,并且有空余空间(即,数组的元素多于此集合),则紧跟集合结尾的数组中的元素将设置为null。
好的,好的,至少记录在案。但它后来说:
仅当调用者知道此集合不包含任何null元素时,这在确定此集合的长度时非常有用。
这是文档中最让我困惑的部分:|
还有一个更有趣的例子对我来说没什么意义:
String[] middle = { "nonZ", "y", "u", "m" };
List<String> list = new ArrayList<>();
list.add("z");
list.add(null);
list.add("z1");
System.out.println(list.size()); // 3
String[] middleNew = list.toArray(middle);
System.out.println(Arrays.toString(middleNew));
这将打印:
[z, null, z1, null]
所以它清除了数组中的最后一个元素,但为什么它不会在第一个例子中那样做呢?
有人可以在这里说清楚吗?
该 <T> T[] toArray(T[] a)
Collection上的方法很奇怪,因为它试图同时实现两个目的。
首先,我们来看看 toArray()
。这将从集合中获取元素并将其返回 Object[]
。也就是说,返回数组的组件类型总是如此 Object
。这很有用,但它不满足其他一些用例:
1)来电者想要 再利用 现有数组,如果可能的话;和
2)调用者想要指定返回数组的组件类型。
处理案例(1)证明是一个相当微妙的API问题。调用者想要重用一个数组,所以它显然需要传入。与no-arg不同 toArray()
方法,它返回一个正确大小的数组,如果调用者的数组被重用,我们需要一种方法来返回复制的元素数。好的,我们有一个看起来像这样的API:
int toArray(T[] a)
调用者传入一个重用的数组,返回值是复制到其中的元素数。不需要返回该数组,因为调用者已经有了对它的引用。但是如果阵列太小会怎么样?好吧,也许抛出异常。事实上,那是什么 Vector.copyInto 确实。
void copyInto(Object[] anArray)
这是一个糟糕的API。它不仅不会返回复制的元素数量,而且还会抛出 IndexOutOfBoundsException
如果目标阵列太短。由于Vector是并发集合,因此调用之前的大小可能会随时更改,因此调用者也是如此 不能 保证目标数组足够大,也不知道复制的元素数。调用者唯一能做的就是围绕整个序列锁定Vector:
synchronized (vec) {
Object[] a = new Object[vec.size()];
vec.copyInto(a);
}
啊!
该 Collections.toArray(T[])
如果目标数组太小,API会通过具有不同的行为来避免此问题。它不是像Vector.copyInto()那样抛出异常,而是分配一个 新 大小合适的数组。这样可以消除阵列重用情况,从而实现更可靠的操作。现在的问题是,调用者无法判断其数组是否已被重用或是否已分配新数组。因此,返回值 toArray(T[])
需要返回一个数组:参数数组,如果它足够大,或者新分配的数组。
但现在我们还有另一个问题。我们不再有办法告诉调用者从集合中复制到数组中的元素数量。如果目标数组是新分配的,或者数组恰好是正确的大小,则数组的长度是复制的元素数。如果目标数组大于复制的元素数,则该方法尝试通过编写一个元素来向调用者传达复制的元素数。 null
到阵列的位置 一个超越 从集合中复制的最后一个元素。如果已知源集合没有空值,则可以使调用者确定复制的元素数。调用之后,调用者可以搜索数组中的第一个空值。如果有,则其位置确定复制的元素数。如果数组中没有null,则它知道复制的元素数等于数组的长度。
坦率地说,这非常蹩脚。但是,考虑到当时语言的限制,我承认我没有更好的选择。
我不认为我曾经见过任何重用数组或以这种方式检查空值的代码。这可能是从内存分配和垃圾收集很昂贵的早期开始的延续,因此人们希望尽可能多地重用内存。最近,使用该方法的公认惯用法是上述第二个用例,即如下建立数组所需的组件类型:
MyType[] a = coll.toArray(new MyType[0]);
(为此目的分配零长度数组似乎很浪费,但事实证明,这种分配可以通过JIT编译器进行优化,并且是明显的替代方案 toArray(new MyType[coll.size()])
实际上比较慢。这是因为需要将数组初始化为null,然后用集合的内容填充它。请参阅Alexey Shipilev关于此主题的文章, 古代智慧阵列。)
然而,许多人发现零长度阵列违反直觉。在JDK 11中,有一个新的API允许使用数组构造函数引用:
MyType[] a = coll.toArray(MyType[]::new);
这允许调用者指定数组的组件类型,但它允许集合提供大小信息。
它只会清除索引中的元素 之后 原始列表中的最后一个元素,因此在第一个示例中,列表为空,因此它使索引为零的元素无效(第一个元素是 "1"
)。
在上一个例子中,最后一个元素恰好是原始列表中最后一个元素之后的元素。知道最后一个场景不会真正有助于确定列表的大小,因为它 没有 允许空值。
但是如果列表不允许null(例如 Java 9中引入的不可变列表),这很有用,因为如果你循环返回的数组, 你不想处理额外的元素,在这种情况下,您可以在第一个null元素处停止迭代器。
来自JDK 9的源代码 ArrayList
:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
并在 Arrays.ArrayList
, List
实现返回 Arrays.asList
:
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
如果要转换为数组的列表的大小是 size
,然后他们都设定了 a[size]
至 null
。
有一个空列表, size
是 0
所以 a[0]
被设定为 null
,并没有触及其他元素。
使用单身人士名单, size
是 1
所以 a[1]
被设定为 null
,并没有触及其他元素。
如果列表的大小比数组的长度小1, a[size]
指的是数组的最后一个元素,因此它被设置为 null
。在你的例子中,你有一个 null
在第二个位置(索引1),所以设置为 null
作为一个元素。如果有人在找 null
要计算元素,他们会停在这里而不是另一个 null
, 哪一个是 null
将下一个元素设置为超出列表内容的结果 null
。这些 null
s不能被分开。
(例如)ArrayList的toArray(T [] a)代码非常清楚:
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
如果输入数组的大小大于此列表(这意味着我们可以将所有列表的内容复制到此数组中,因为它的长度足够大),那么在所有列表内容复制之后,数组中的下一个元素引用(实际上索引等于列表的大小)将设置为null。