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>
(在运行时T
在List<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]; }
它是在编译时用Object
replaceE
的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