ArrayList.toArray()中的Java泛型

时间:2016-04-13 12:49:14

标签: java arrays generics

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

ArrayList<String> someData = new ArrayList<>();

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

String someLine = someData.get(0);

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

String[] arrayOfData = someData.toArray();

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

8 个答案:

答案 0 :(得分:57)

如果你看一下ArrayList<E>toArray(T[] a)的实现,就像:

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

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

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

但问题在于您无法在Java中创建通用数组,因为编译器并不确切知道T代表什么。换句话说,Java中不允许创建不可重复类型的数组JLS §4.7

数组存储异常JLS §10.5)的另一个重要引用:

  

如果数组的组件类型不可重新生成(第4.7节),则Java虚拟机无法执行   前段。这就是数组创建表达式的原因   禁止使用不可重复的元素类型(第15.10.1节)。

这就是为什么Java提供了重载版本toArray(T[] a)

  

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

因此,您应该使用toArray()

,而不是覆盖toArray(T[] a) 来自Java Doc的

Cannot Create Instances of Type Parameters也可能对您有用。

答案 1 :(得分:20)

通用信息在运行时为erased。 JVM不知道您的列表是List<String>还是List<Integer>T中的运行时List<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());

答案 2 :(得分:14)

如果您查看Javadoc for the List interface,您会发现第二种形式的toArray<T> T[] toArray(T[] a)

事实上,Javadoc甚至举例说明了如何做你想做的事情:

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

答案 3 :(得分:5)

  

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

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

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

public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

可以在类中使用public E get(int index)等通用方法。

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

在运行时创建数组并且由于Type erasure,Java运行时没有E所代表的类型的特定信息。到目前为止,唯一的解决方法是将所需类型作为参数传递给方法,从而传递签名public <T> T[] toArray(T[] a),其中客户端被强制传递所需类型。

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

E elementData(int index) {
    return (E) elementData[index];
}

Java编译器在编译时将E替换为Object

答案 4 :(得分:5)

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

另一方面,泛型中的类型参数在运行时不存在。 ArrayList<String>ArrayList<Integer>之间在运行时完全没有区别。这只是ArrayList

这就是为什么你不能在不以某种方式单独传递组件类型的情况下获取List<String>并获得String[]的基本原因 - 你必须得到组件从没有组件类型信息的东西中输入信息。显然,这是不可能的。

答案 5 :(得分:1)

数组的类型与数组的类型不同。它是StringArray类而不是String类。

假设,通用方法toArray()可能看起来像

private <T> T[] toArray() {
    T[] result = new T[length];
    //populate
    return result;
}

现在在编译期间,类型T被删除。如何替换new T[length]部分?通用类型信息不可用。

如果查看(例如)ArrayList的源代码,您会看到相同的内容。 toArray(T[] a)方法填充给定数组(如果大小匹配)或使用参数的类型创建新的新数组,该参数是通用类型T的数组类型。

答案 6 :(得分:1)

您必须首先理解的是ArrayList拥有的只是Object的数组

   transient Object[] elementData;

当谈到T[]失败的原因时,它是因为你不能得到一个没有Class<T>的泛型类型的数组,这是因为java的类型擦除(there is a more explanationhow to create one)。并且堆上的array[]动态地知道它的类型,并且您不能将int[]强制转换为String[]。同样的原因,您无法将Object[]投射到T[]

   int[] ints = new int[3];
   String[] strings = (String[]) ints;//java: incompatible types: int[] cannot be converted to java.lang.String[]

   public <T> T[] a() {
      Object[] objects = new Object[3];
      return (T[])objects;
   }
   //ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
   Integer[] a = new LearnArray().<Integer>a();

但是你放入array的只是一个类型为E的对象(由编译器检查),所以你可以把它转换为E,这是安全的正确的。

  return (E) elementData[index];

简而言之,你无法通过演员获得不具备的东西。您只有Object[],因此toArray()只能返回Object[](否则,您必须给它Class<T>来制作此类型的新数组)。您将E放入ArrayList<E>,您可以E获得get()

答案 7 :(得分:0)

可以创建给定(已知)类型的“通用”数组。通常,我在代码中使用类似的内容。

public static <T> T[] toArray(Class<T> type, ArrayList<T> arrList) {
    if ((arrList == null) || (arrList.size() == 0)) return null;
    Object arr = Array.newInstance(type, arrList.size());
    for (int i=0; i < arrList.size(); i++) Array.set(arr, i, arrList.get(i));
    return (T[])arr;
}