java.util.ArrayList <T> .toArray()可以变得更友好吗?

Don*_*tch 5 java collections arraylist

我很惊讶使用java.util.ArrayList <T> .toArray()是多么痛苦.

假设我将我的数组列表声明为:

java.util.ArrayList<double[]> arrayList = new java.util.ArrayList<double[]>();
... add some items ...
Run Code Online (Sandbox Code Playgroud)

然后要将其转换为数组,我必须执行以下操作之一:

double[][] array = (double[][])arrayList.toArray(new double[0][]);
Run Code Online (Sandbox Code Playgroud)

要么:

double[][] array = (double[][])arrayList.toArray(new double[arrayList.size()][]);
Run Code Online (Sandbox Code Playgroud)

要么:

double[][] array = new double[arrayList.size()];
arrayList.toArray(array);
Run Code Online (Sandbox Code Playgroud)

以上都不是非常易读.我不应该说以下吗?

double[][] array = arrayList.toArray();
Run Code Online (Sandbox Code Playgroud)

但是这会产生编译错误,因为Object []无法转换为double [] [].

也许这是不可能的,因为toArray必须返回Object []以便与模板前的日子向后兼容.但是,如果是这种情况,不能用更好的名称添加更友好的替代方法吗?我想不出一个好名字,但几乎所有东西都比现有方法更好; 例如,以下情况可以:

double[][] array = arrayList.toArrayOfNaturalType();
Run Code Online (Sandbox Code Playgroud)

没有这样的成员函数,但也许可以编写一个通用的辅助函数来完成它?

double[][] array = MyToArray(arrayList);
Run Code Online (Sandbox Code Playgroud)

MyToArray的签名如下:

public static <T> T[] MyToArray(java.util.ArrayList<T> arrayList)
Run Code Online (Sandbox Code Playgroud)

是否可以实现这样的功能?我实现它的各种尝试导致编译错误"错误:通用数组创建"或"错误:无法从类型变量中选择".

这是我能得到的最接近的:

public static <T> T[] MyToArray(java.util.ArrayList<T> arrayList, Class type)
{
    T[] array = (T[])java.lang.reflect.Array.newInstance(type, arrayList.size());
    arrayList.toArray(array);
    return array;
}
Run Code Online (Sandbox Code Playgroud)

它被称为这样:

double[][] array = MyToArray(arrayList, double[].class);
Run Code Online (Sandbox Code Playgroud)

我希望冗余的最终参数不存在,但即便如此,我认为这是迄今为止我看到的将阵列列表转换为数组的最不可怕的方式.

有可能比这更好吗?

Ste*_*n C 5

有可能比这更好吗?

不.

以上都不是非常易读.我不应该说以下吗?

double[][] array = arrayList.toArray();
Run Code Online (Sandbox Code Playgroud)

这会很好......但你做不到.

问题是该toArray()方法是在Java 1.2中以您所看到的行为指定的.在Java 1.5之前,通用类型未添加到该语言中.添加它们后,设计人员选择了"类型擦除"方法,以便与早期版本的Java兼容.所以:

  • 如果toArray()不破坏兼容性,方法的语义就无法改变
  • 类型擦除使得toArray()方法实现无法知道列表的实际元素类型是什么,因此无论如何它都无法正确执行.


Stu*_*rks 4

不幸的是你不能写

double[][] array = arrayList.toArray();
Run Code Online (Sandbox Code Playgroud)

原因是toArray()JDK 1.2(泛型之前)中定义了 return Object[]。这无法兼容地更改。

泛型是在 Java 5 中引入的,但是使用擦除来实现的。这意味着ArrayList实例在运行时不知道它包含的对象的类型;因此,它无法创建所需元素类型的数组。这就是为什么您必须传递某种类型标记(在本例中是一个实际的数组实例)来告诉ArrayList要创建的数组的类型。

你应该能够写

double[][] array = arrayList.toArray(new double[0][]);
Run Code Online (Sandbox Code Playgroud)

没有演员。的单参数重载toArray()已泛化,因此您将获得正确的返回类型。

人们可能认为传递预先确定大小的数组而不是一次性的零长度数组更好。Aleksey Shipilev 写了一篇文章分析这个问题。答案是,有点违反直觉,创建零长度数组可能会更快。

简而言之,原因是分配很便宜,零长度数组很小,并且它可能会被丢弃并很快被垃圾收集,这也很便宜。相比之下,创建预先确定大小的数组需要对其进行分配,然后用空值/零填充。然后将其传递给toArray(),然后用列表中的值填充它。因此,每个数组元素通常被写入两次。通过将零长度数组传递给toArray(),这允许数组分配发生在与数组填充代码相同的代码中,从而为 JIT 编译器提供了绕过初始零填充的机会,因为它知道每个数组元素都将是填充。

还有JDK-8060192建议添加以下内容:

<A> A[] Collection.toArray(IntFunction<A[]> generator)
Run Code Online (Sandbox Code Playgroud)

这使您可以传递一个给定数组大小的 lambda 表达式,并返回一个已创建的具有该大小的数组。(这类似于Stream.toArray()。)例如,

// NOT YET IMPLEMENTED
double[][] array = arrayList.toArray(n -> new double[n][]);
double[][] array = arrayList.toArray(double[][]::new);
Run Code Online (Sandbox Code Playgroud)

这还没有实现,但我仍然希望它可以进入 JDK 9。

您可以按照以下方式重写辅助函数:

static <T> T[] myToArray(List<T> list, IntFunction<T[]> generator) {
    return list.toArray(generator.apply(list.size()));
}
Run Code Online (Sandbox Code Playgroud)

(请注意,列表的并发修改存在一些微妙之处,在本示例中我忽略了这一点。)这将允许您编写:

double[][] array = myToArray(arrayList, double[][]::new);
Run Code Online (Sandbox Code Playgroud)

这还不错。但实际上并不清楚它是否比仅仅分配一个零长度数组传递给toArray().

最后,人们可能会问为什么toArray()采用实际的数组实例而不是Class对象来表示所需的元素类型。Joshua Bloch(Java 集合框架的创建者)在JDK-5072831的评论中表示,这是可行的,但他不确定这是一个好主意,尽管他可以接受。

这里还有一个额外的用例,将元素复制到现有数组中,就像旧Vector.copyInto()方法一样。数组承载toArray(T[])方法也支持这种用例。事实上,它更好,Vector.copyInto()因为如果集合的大小发生变化,后者在并发修改的情况下不能安全地使用。的自动调整大小行为可以toArray(T[])处理此问题,并且还可以处理创建调用者所需类型的数组的情况,如上所述。因此,虽然添加接受对象的重载Class肯定会起作用,但它并没有在现有 API 上添加太多内容。