java.util.ArrayList <t> .toArray()会变得更友好吗?

时间:2016-06-04 02:55:16

标签: java arraylist collections

我很惊讶使用java.util.ArrayList&lt; T&gt; .toArray()是多么痛苦。

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

java.util.ArrayList<double[]> arrayList = new java.util.ArrayList<double[]>();
... add some items ...

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

double[][] array = (double[][])arrayList.toArray(new double[0][]);

或:

double[][] array = (double[][])arrayList.toArray(new double[arrayList.size()][]);

或:

double[][] array = new double[arrayList.size()];
arrayList.toArray(array);

以上都不是非常易读。难道我不能说以下内容吗?

double[][] array = arrayList.toArray();

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

也许这是不可能的,因为toArray必须返回Object [] 为了与模板前的日子向后兼容。 但如果是这样的话,就不能添加更友好的替代方法 用不同的名字?我不能想出一个好名字,但几乎可以想到什么 会比现有的方式更好;例如以下情况可以:

double[][] array = arrayList.toArrayOfNaturalType();

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

double[][] array = MyToArray(arrayList);

MyToArray的签名如下:

public static <T> T[] MyToArray(java.util.ArrayList<T> arrayList)

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

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

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;
}

它的名字是这样的:

double[][] array = MyToArray(arrayList, double[].class);

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

有可能比这更好吗?

2 个答案:

答案 0 :(得分:5)

  

有可能比这更好吗?

不。

  

以上都不是非常易读。难道我不能说以下内容吗?

double[][] array = arrayList.toArray();

这会很好......但你不能。

问题是toArray()方法是在Java 1.2中以您所看到的行为指定的。在Java 1.5之前,通用类型未添加到该语言中。当它们被添加时,设计师选择了&#34;类型擦除&#34;方法,与早期版本的Java兼容。所以:

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

答案 1 :(得分:4)

不幸的是你不能写

double[][] array = arrayList.toArray();

原因是在JDK 1.2中定义toArray()(在泛型之前)以返回Object[]。这不能兼容地改变。

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

你应该可以写

double[][] array = arrayList.toArray(new double[0][]);

没有演员。 toArray()的一个arg重载是通用的,因此您将获得正确的返回类型。

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

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

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

<A> A[] Collection.toArray(IntFunction<A[]> generator)

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

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

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

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

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

(请注意,这里有一些细微的同时修改列表,我在这个例子中忽略了。)这可以让你写:

double[][] array = myToArray(arrayList, double[][]::new);

并非特别糟糕。但实际上并不清楚它只是分配一个零长度数组传递给toArray()

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

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