我不理解泛型和数组之间的联系。
我可以使用泛型类型创建数组引用:
private E[] elements; //GOOD
但无法使用泛型类型创建数组对象:
elements = new E[10]; //ERROR
但它有效:
elements = (E[]) new Object[10]; //GOOD
您不应混淆数组和泛型。它们不能很好地搭配使用。数组和泛型类型强制类型检查的方式有所不同。我们说数组是实例化的,但泛型不是。因此,您会看到在处理数组和泛型时存在这些差异。
什么意思?您现在必须知道以下赋值是有效的:
Object[] arr = new String[10];
基本上讲,Object[]
是 String[]
的超类型,因为 Object
是 String
的超类型。但泛型并非如此。因此,以下声明无效并且不会编译:
List<Object> list = new ArrayList<String>(); // Will not compile.
原因是泛型是不变的。
Java中引入了泛型来在编译时强制进行更强的类型检查。因此,由于类型擦除,泛型类型在运行时没有任何类型信息。因此,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>[]
类型的数组呢?问题是一样的。由于类型擦除,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.
List<String>[]
和List<Integer>[]
都在运行时编译为List[]
。List<?>
是可重用类型。这很有道理,因为根本没有关联的类型。因此,在类型擦除的结果中没有任何损失。因此,创建此类类型的数组是完全类型安全的。List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>(); // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine
以上两种情况都是可以的,因为List<?>
是所有泛型类型实例化的超类型。所以,在运行时它不会发出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;
}
需要进行类型转换,因为该方法返回一个 Object
对象。但你可以确定这是一次安全的转换。因此,你甚至可以在该变量上使用 @SuppressWarnings。
new List<?>[] { }
是有效的 - 只是通配符不能被限定。 - Paul BelloraString[]
,这肯定会失败。我已经编辑了那一部分,使其更加清晰明了。 - Rohit Jain这是 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
是数组的大小。问题在于,运行时擦除了泛型类型,所以new E[10]
将等同于new Object[10]
.
这样做是危险的,因为可能会在数组中放入其他类型而不是E
类型的数据。这就是为什么你需要通过以下方式明确指定你想要的类型:
E[]
数组,或者componentType
参数的类型的实际数组实例。new E[size]
不被允许是为了防止认为我们实际上正在创建 E
类型的数组而不是 Object
类型的数组。我在答案中提到的方法清楚地显示了正在发生什么,而 new E[size]
可能会被错误解释。但再次强调,这只是我的猜测。 - Pshemonew E[size]
,因为它不会创建E[]
数组(仅包含E类型的数组),而是Object[]
数组。 - Pshemo选中:
public Constructor(Class<E> c, int length) {
elements = (E[]) Array.newInstance(c, length);
}
public Constructor(int s) {
elements = new Object[s];
}
new ArrayList<E>()
吗? - micha