<T> T[] toArray(T[] a)Collection上的方法很奇怪,因为它试图同时实现两个目的。
首先,让我们看一下
toArray()。这将从集合中获取元素,并将其返回到中
Object[]。也就是说,返回数组的组件类型始终为
Object。这很有用,但不能满足其他几个用例:
1)如果可能,调用者想 重用 现有的数组;和
2)调用者想指定返回数组的组件类型。
处理情况(1)原来是一个相当微妙的API问题。调用方想重用一个数组,因此显然需要传递它。与no-arg
toArray()方法不同,no-arg
方法返回正确大小的数组,如果调用方的数组被重用,我们需要一种方法返回复制的元素数。好的,让我们有一个看起来像这样的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,则知道复制的元素数等于数组的长度。
坦白说,这真是la脚。但是,考虑到当时对语言的限制,我承认我没有更好的选择。
我认为我从未见过任何重用数组或以这种方式检查null的代码。这可能是内存分配和垃圾回收昂贵的早期阶段的一个保留,因此人们希望尽可能多地重用内存。最近,使用此方法的惯用语已成为上述第二个用例,即按如下所述建立所需的数组组件类型:
MyType[] a = coll.toArray(new MyType[0]);
(为此目的分配一个零长度的数组似乎很浪费,但是事实证明,JIT编译器可以优化这种分配,并且明显的替代方案
toArray(newMyType[coll.size()])实际上要慢一些。这是因为需要将数组初始化为,然后将其填充为集合的内容。有关此主题,请参见Alexey
Shipilev的文章 “古代智慧的数组” 。)
但是,许多人发现零长度数组是违反直觉的。在JDK 11中,有一个新的API,它允许人们使用数组构造函数引用:
MyType[] a = coll.toArray(MyType[]::new);
这使调用者可以指定数组的组件类型,但可以让集合提供大小信息。



