我不理解泛型和数组之间的联系。
我可以使用泛型类型创建数组引用:
private E[] elements; //GOOD
但无法使用泛型类型创建数组对象:
elements = new E[10]; //ERROR
但它有效:
elements = (E[]) new Object[10]; //GOOD
答案 0 :(得分:180)
你不应该混淆数组和泛型。他们并不顺利。数组和泛型类型如何强制执行类型检查存在差异。我们说阵列是有效的,但泛型不是。因此,您会发现这些差异适用于数组和泛型。
这意味着什么?您现在必须知道以下分配是有效的:
Object[] arr = new String[10];
基本上,Object[]
是String[]
的超级类型,因为Object
是String
的超类型。泛型不是这样。因此,以下声明无效,不会编译:
List<Object> list = new ArrayList<String>(); // Will not compile.
原因是,泛型是不变的。
在Java中引入了泛型,以便在编译时强制执行更强大的类型检查。因此,由于type erasure,泛型类型在运行时没有任何类型信息。因此,List<String>
的静态类型为List<String>
,但动态类型为List
。
但是,数组随身携带组件类型的运行时类型信息。在运行时,数组使用Array Store检查来检查是否插入与实际数组类型兼容的元素。那么,以下代码:
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>[]
类型的数组?问题是一样的。由于类型擦除,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[]
。
是。原因是,List<?>
是一种可再生类型。这是有道理的,因为根本没有相关的类型。因此,类型擦除不会导致任何问题。因此,创建这种类型的数组是完全类型安全的。
List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>(); // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine
上述情况都很好,因为List<?>
是泛型List<E>
的所有实例化的超类型。因此,它不会在运行时发出ArrayStoreException。案例与原始类型数组相同。由于原始类型也是可再生类型,因此您可以创建数组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;
}
需要Typecast,因为该方法返回Object
。但你可以肯定这是一个安全的演员阵容。因此,您甚至可以对该变量使用@SuppressWarnings。
答案 1 :(得分:3)
以下是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
是数组的大小。
答案 2 :(得分:3)
问题是,虽然运行时generic type is erased因此new E[10]
等同于new Object[10]
。
这将是危险的,因为可以放入除E
类型之外的其他数据。这就是为什么你需要通过
E[]
数组或componentType
argiment中传递的类型数组的实例。答案 3 :(得分:1)
已选中:
public Constructor(Class<E> c, int length) {
elements = (E[]) Array.newInstance(c, length);
}
或未选中:
public Constructor(int s) {
elements = new Object[s];
}