【问题标题】:Could java.util.ArrayList<T>.toArray() be made friendlier?java.util.ArrayList<T>.toArray() 可以变得更友好吗?
【发布时间】:2016-06-04 02:55:16
【问题描述】:

我对使用 java.util.ArrayList.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)

这样的功能可以实现吗? 我实现它的各种尝试导致编译错误 “错误:创建泛型数组”或“错误:无法从类型变量中选择”。

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

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

我希望多余的最终参数不存在,但即便如此, 我认为这是迄今为止我见过的将数组列表转换为数组的最不可怕的方法。

还有比这更好的吗?

【问题讨论】:

  • 根本问题是由于擦除,在运行时类型 T 是未知的,因此如果没有一些帮助,您无法创建 T[]。
  • 有什么理由绝对需要像List&lt;double[]&gt; 这样的类型吗?泛型和数组不能混用。
  • @dimo414 我到底想要的是一个double[][2];我正在使用 ArrayList 逐步构建它,因为我事先不知道最终的大小。 ArrayList 不适合做这个吗?
  • 使用List&lt;List&lt;Double&gt;&gt;。如果您必须,您可以将最终结果复制到数组中,但这很少需要。只需在整个应用程序中坚持使用集合类型,您就会更快乐。

标签: java arraylist collections


【解决方案1】:

还有比这更好的吗?

没有。

以上都不是非常可读的。我不应该说以下吗?

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

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

问题在于 toArray() 方法早在 Java 1.2 中就已经指定了您所看到的行为。直到 Java 1.5 才将泛型类型添加到该语言中。添加它们时,设计人员选择了“类型擦除”方法,以与早期版本的 Java 兼容。所以:

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

【讨论】:

  • +1 在这里提供一些历史记录。根据 OP 的要求,我已将其中一些材料纳入我的回答中。
【解决方案2】:

很遗憾你不会写

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

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

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

你应该会写

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

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

有人可能会认为最好传递一个预先确定大小的数组,而不是一次性的零长度数组。 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 的 cmets 中表示,这是可行的,但他不确定这是一个好主意,尽管他可以接受。

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

【讨论】:

  • 哦!感谢您提供有关为什么零长度分配比其他分配更快的链接和概要——我在eclipsesource.com/blogs/2014/04/11/… 看到了这个事实,并被它迷惑了。这都是非常棒的信息。
  • 我接受这个是因为它很棒而且填补了我的知识空白。但是我认为要形成一个完整的画面,需要在@StephenC 的回答中提到类型擦除,以解释为什么最简单的 API 是不可能的。
  • 谢谢!更新了更多历史和基本原理。
猜你喜欢
  • 2016-02-01
  • 2010-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
  • 2020-12-21
  • 1970-01-01
  • 2015-04-08
相关资源
最近更新 更多