如何创建泛型数组?
我不明白泛型和数组之间的联系。
我可以创建具有泛型类型的数组引用:
private E[] elements; //GOOD
但无法使用泛型类型创建数组对象:
elements = new E[10]; //ERROR
但它的工作原理:
elements = (E[]) new Object[10]; //GOOD
我不明白泛型和数组之间的联系。
我可以创建具有泛型类型的数组引用:
private E[] elements; //GOOD
但无法使用泛型类型创建数组对象:
elements = new E[10]; //ERROR
但它的工作原理:
elements = (E[]) new Object[10]; //GOOD
不应混淆数组和泛型。他们不能很好地结合在一起。数组和泛型类型强制执行类型检查的方式存在差异。我们说数组是重新初始化的,但泛型不是。因此,您会看到这些差异适用于数组和泛型。
这意味着什么?您现在必须知道以下分配是有效的:
Object[] arr = new String[10];
基本上,an 是的超类型,因为是 超类型的 。对于泛型,情况并非如此。因此,以下声明无效,并且不会编译:Object[]
String[]
Object
String
List<Object> list = new ArrayList<String>(); // Will not compile.
原因是,泛型是不变的。
在Java中引入了泛型,以便在编译时强制执行更强的类型检查。因此,由于类型擦除,泛型类型在运行时没有任何类型信息。因此,a 具有 静态类型,但 动态类型为 。List<String>
List<String>
List
但是,数组携带组件类型的运行时类型信息。在运行时,数组使用数组存储检查来检查是否插入与实际数组类型兼容的元素。因此,以下代码:
Object[] arr = new String[10];
arr[0] = new Integer(10);
将编译良好,但在运行时将失败,这是 ArrayStoreCheck 的结果。对于泛型,这是不可能的,因为编译器将通过提供编译时检查来尝试防止运行时异常,方法是避免创建这样的引用,如上所示。
创建其组件类型为类型参数、具体参数化类型或有界通配符参数化类型的数组是类型不安全的。
请考虑如下代码:
public <T> T[] getArray(int size) {
T[] arr = new T[size]; // Suppose this was allowed for the time being.
return arr;
}
由于 在运行时不知道 的类型,因此创建的数组实际上是 .因此,上述方法在运行时将如下所示:T
Object[]
public Object[] getArray(int size) {
Object[] arr = new Object[size];
return arr;
}
现在,假设您将此方法称为:
Integer[] arr = getArray(10);
这就是问题所在。您刚刚将 一个分配给 的引用。上面的代码可以正常编译,但在运行时会失败。Object[]
Integer[]
这就是禁止创建泛型数组的原因。
new Object[10]
E[]
现在你最后的疑问,为什么下面的代码有效:
E[] elements = (E[]) new Object[10];
上述代码具有与上述相同的含义。如果您注意到,编译器会在那里为您提供“未选中的强制转换警告”,因为您正在向未知组件类型的数组进行类型转换。这意味着,强制转换可能会在运行时失败。例如,如果您在上述方法中有该代码:
public <T> T[] getArray(int size) {
T[] arr = (T[])new Object[size];
return arr;
}
你这样调用它:
String[] arr = getArray(10);
这将在运行时使用ClassCastException失败。因此,不,这种方式不会总是起作用。
List<String>[]
问题是一样的。由于类型擦除,a 只不过是 .因此,如果允许创建这样的数组,让我们看看会发生什么:List<String>[]
List[]
List<String>[] strlistarr = new List<String>[10]; // Won't compile. but just consider it
Object[] objarr = strlistarr; // this will be fine
objarr[0] = new ArrayList<Integer>(); // This should fail but succeeds.
现在,在上面的例子中,ArrayStoreCheck将在运行时成功,尽管这应该会引发一个ArrayStoreException。这是因为两者都是在运行时编译的。List<String>[]
List<Integer>[]
List[]
是的。原因是,a 是可再生类型。这是有道理的,因为根本没有关联的类型。因此,由于类型擦除,没有什么可丢失的。因此,创建此类类型的数组是完全类型的。List<?>
List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>(); // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine
以上两种情况都很好,因为是泛型类型的所有实例化的超类型。因此,它不会在运行时发出 ArrayStoreException。原始类型数组的情况相同。由于原始类型也是可重用类型,因此您可以创建一个数组 。List<?>
List<E>
List[]
所以,它就像是,你只能创建一个可重用类型的数组,而不能创建不可重用的类型。请注意,在上述所有情况下,数组的声明都很好,它是使用运算符创建数组,这会产生问题。但是,声明这些引用类型的数组是没有意义的,因为它们不能指向任何东西,只能(忽略无界类型)。new
null
E[]
是的,您可以使用 Array#newInstance()
方法创建数组:
public <E> E[] getArray(Class<E> clazz, int size) {
@SuppressWarnings("unchecked")
E[] arr = (E[]) Array.newInstance(clazz, size);
return arr;
}
需要类型转换,因为该方法返回 .但你可以肯定这是一个安全的演员。因此,您甚至可以在该变量上使用@SuppressWarnings。Object
以下是以下实现:LinkedList<T>#toArray(T[])
public <T> T[] toArray(T[] a) {
if (a.length < size)
a = (T[])java.lang.reflect.Array.newInstance(
a.getClass().getComponentType(), size);
int i = 0;
Object[] result = a;
for (Node<E> x = first; x != null; x = x.next)
result[i++] = x.item;
if (a.length > size)
a[size] = null;
return a;
}
简而言之,您只能通过数组大小的位置创建泛型数组。Array.newInstance(Class, int)
int