假设你有一个arraylist定义如下:
ArrayList<String> someData = new ArrayList<>();
稍后在您的代码中,由于泛型,您可以这样说:
String someLine = someData.get(0);
并且编译器完全知道它将获得一个字符串。是的仿制药!但是,这将失败:
String[] arrayOfData = someData.toArray();
toArray()
将始终返回一个Object数组,而不是已定义的泛型数组。为什么get(x)
方法知道它返回的是什么,但toArray()
默认为对象?
答案 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的
答案 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 explanation和how 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;
}