ArrayList.toArray()中的Java泛型

Rab*_*Guy 58 java arrays generics

假设你有一个arraylist定义如下:

ArrayList<String> someData = new ArrayList<>();
Run Code Online (Sandbox Code Playgroud)

稍后在您的代码中,由于泛型,您可以这样说:

String someLine = someData.get(0);
Run Code Online (Sandbox Code Playgroud)

并且编译器完全知道它将获得一个字符串.是的仿制药!但是,这将失败:

String[] arrayOfData = someData.toArray();
Run Code Online (Sandbox Code Playgroud)

toArray()将始终返回一个对象数组,而不是已定义的泛型数组.为什么该get(x)方法知道它返回什么,但toArray()默认为Objects?

sud*_*d29 57

如果你看一下执行toArray(T[] a)的ArrayList <E>类,它是这样的:

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;
}
Run Code Online (Sandbox Code Playgroud)

此方法的问题是您需要传递相同泛型类型的数组.现在考虑这个方法是否不采用任何参数,那么实现将类似于:

public <T> T[] toArray() {
    T[] t = new T[size]; // compilation error
    return Arrays.copyOf(elementData, size, t.getClass());
}
Run Code Online (Sandbox Code Playgroud)

但问题是你不能用Java创建通用数组,因为编译器并不确切知道T代表什么.换句话说,Java中不允许创建不可重类型的数组(JLS§4.7).

Array Store Exception的另一个重要引用(JLS§10.5):

如果数组的组件类型不可重新生成(第4.7节),则Java虚拟机无法执行前一段中描述的存储检查.这就是为什么禁止使用具有不可恢复元素类型的数组创建表达式(第15.10.1节).

这就是Java提供重载版本的原因toArray(T[] a).

我将覆盖toArray()方法,告诉它它将返回一个E数组.

因此toArray(),您应该使用,而不是覆盖toArray(T[] a).

无法从Java Doc 创建类型参数实例也可能对您有意义.

  • @jpaugh Java确实允许这样做,它甚至可以推断出基于LHS的赋值的RHS类型.您还可以在调用泛型方法时显式声明类型参数.例如,请参阅http://ideone.com/AObtRR. (7认同)
  • 我认为你抓住了问题的关键,简单地说Java不能创建泛型类型的数组. (4认同)

Ada*_*ker 20

通用信息在运行时被擦除.JVM不知道你的列表是否是List<String>List<Integer>(在运行TList<T>被解析为Object),所以唯一可能的数组类型Object[].

你可以使用toArray(T[] array)- 在这种情况下JVM可以使用给定数组的类,你可以在ArrayList实现中看到它:

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());
Run Code Online (Sandbox Code Playgroud)

  • 您的答案是正确的,但对于非Java专家来说并不容易理解.重要的事实是返回的数组必须在运行时创建( - >缺少什么类型的信息),其中`get(..)`只返回一个现有的对象. (5认同)

ach*_*ach 16

如果你看一下界面JavadocList,你会发现第二种形式toArray:<T> T[] toArray(T[] a).

实际上,Javadoc甚至提供了一个如何完成您想要做的事情的示例:

String[] y = x.toArray(new String[0]);


sen*_*iwu 5

我可以,并且有时会使用迭代器而不是制作数组,但这对我来说似乎总是很奇怪.为什么get(x)方法知道它返回什么,但toArray()默认为Objects?它就像设计它的一半,他们决定这里不需要这个?

由于问题的目的似乎不仅仅是围绕使用toArray()泛型,而是要了解ArrayList类中方法的设计,我想补充:

ArrayList 是一个泛型类,因为它被声明为

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Run Code Online (Sandbox Code Playgroud)

这使得可以使用诸如public E get(int index)类内的通用方法.

但是如果一个方法toArray()没有返回E,E[]那么事情开始变得有点棘手.不可能提供签名,例如public <E> E[] toArray()因为无法创建通用数组.

数组的创建发生在运行时,由于类型擦除,Java运行时没有表示类型的特定信息E.到目前为止,唯一的解决方法是将所需类型作为参数传递给方法,从而将public <T> T[] toArray(T[] a)客户端强制传递所需类型的签名传递给该方法.

但另一方面,它适用于public E get(int index)因为如果你看一下方法的实现,你会发现即使该方法使用相同的Object数组来返回指定索引处的元素,它也会被转换为E

E elementData(int index) {
    return (E) elementData[index];
}
Run Code Online (Sandbox Code Playgroud)

它是Java编译器这在编译时替换EObject


new*_*cct 5

需要注意的是,Java中的数组在运行时知道它们的组件类型.String[]并且Integer[]在运行时是不同的类,您可以在运行时向数组询问其组件类型.因此,在运行时需要组件类型(通过在编译时使用new String[...]或使用Array.newInstance()并传递类对象对可重新构造的组件类型进行硬编码)来创建数组.

另一方面,泛型中的类型参数在运行时不存在.ArrayList<String>a和a 之间在运行时完全没有区别ArrayList<Integer>.这一切都很公正ArrayList.

这就是为什么你不能仅仅以某种方式单独传递组件类型来获取List<String>和获取一个基本原因String[]- 你必须从没有组件类型信息的东西中获取组件类型信息.显然,这是不可能的.