ArrayList.toArray()中的Javagenerics

假设你有一个数组列表定义如下:

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

后来在你的代码中,由于generics,你可以这样说:

 String someLine = someData.get(0); 

编译器知道它会得到一个string。 耶generics! 但是,这将失败:

 String[] arrayOfData = someData.toArray(); 

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

如果你看一下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; } 

这个方法的问题是你需要传递相同的genericstypes的数组。 现在考虑一下,如果这个方法没有任何争论,那么实现将类似于:

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

但是这里的问题是你不能用Java创build通用数组,因为编译器不知道T代表什么。 换句话说,Java中不允许 创build不可重定义types的数组 ( JLS§4.7 )。

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

如果数组的组件types不可确定(§4.7),那么Java虚拟机将无法执行上一段中描述的存储检查。 这就是为什么具有不可指定元素types的数组创buildexpression式被禁止的原因(§15.10.1)。

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

我将重写toArray()方法来告诉它它将返回一个E的数组。

所以,而不是重写toArray() ,你应该使用toArray(T[] a)

无法从Java文档创buildtypes参数的实例也可能对您有所帮助。

通用信息在运行时被删除 。 JVM不知道你的列表是List<String>还是List<Integer> (在运行时TList<T>被parsing为Object ),所以唯一可能的数组types是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()); 

如果您查看List接口的Javadoc ,则会注意到toArray的第二种forms: <T> T[] toArray(T[] a)

事实上,Javadoc甚至给出了一个如何做你想做的事情的例子:

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

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

另一方面,generics中的types参数在运行时不存在。 在ArrayList<String>ArrayList<Integer>之间的运行时绝对没有区别。 这只是ArrayList

这就是为什么你不能以某种方式单独传入组件types而仅仅接受一个List<String>并得到一个String[]的根本原因 – 你必须从没有组件types的东西中获取组件types信息信息。 显然,这是不可能的。

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

由于这个问题的意图似乎不仅仅是绕过generics使用toArray() ,而是关于理解ArrayList类中方法的devise,我想补充一下:

ArrayList是一个generics类,就像它声明的那样

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

这使得在类中使用generics方法(如public E get(int index)成为可能。

但是如果像toArray()这样的方法没有返回E ,而是E[]那么事情就会变得有点棘手。 不可能提供诸如public <E> E[] toArray()类的签名,因为不可能创buildgenerics数组。

在运行时创build数组,并且由于Type擦除 ,Java运行时没有由E表示的types的具体信息。 现在唯一的解决方法是将所需的types作为parameter passing给方法,并因此将客户端强制传递所需types的签名public <T> T[] toArray(T[] a)

但另一方面,它适用于public E get(int index)因为如果您查看方法的实现,您会发现即使该方法使用相同的Object数组来返回指定的元素指数,它被铸造到E

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

它是在编译时用ObjectreplaceE的Java编译器

数组的types与数组的types不同。 这是StringArray类,而不是String类。

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

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

现在在编译期间,typesT被擦除。 new T[length]部分应该如何replace? 通用types信息不可用。

如果您查看(例如) ArrayList的源代码,您将看到相同的结果。 toArray(T[] a)方法既可以填充给定的数组(如果大小匹配),也可以使用参数types(即通用typesT的数组types)创build新的新数组。

你必须明白的第一件事是ArrayList自己只是一个Object数组

  transient Object[] elementData; 

当谈到T[]失败的原因时,这是因为如果没有Class<T>就不能获得genericstypes的数组,这是因为Java的types擦除( 有更多的解释以及如何创build ) 。 而且堆上的array[]dynamic的知道它的types,你不能把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中的东西只是一个types为E的对象(由编译器检查),所以你可以把它转换成E ,这是安全和正确的。

  return (E) elementData[index]; 

总之,你不能得到什么没有演员。 你只有Object[] ,所以toArray()可以返回Object[] (否则,你必须给它一个Class<T>来创build这个types的新数组)。 你把E放在ArrayList<E> ,你可以用get()获得一个E