如何创建一个通用数组?

我不明白泛型和数组之间的联系。

我可以使用泛型类型创建数组引用:

private E[] elements; //GOOD 

但是不能用泛型类型创建数组对象:

 elements = new E[10]; //ERROR 

但它的作品:

 elements = (E[]) new Object[10]; //GOOD 

你不应该混淆数组和泛型。 他们不一起走得很好。 数组和泛型类型如何强制执行类型检查存在差异。 我们说阵列是通用的,但是泛型不是。 因此,您会发现这些与数组和泛型有关的差异。

数组是协变的,泛型不是:

那意味着什么? 您现在必须知道下列分配是有效的:

 Object[] arr = new String[10]; 

基本上, Object[]String[]的超类型,因为ObjectString的超类型。 泛型不适用。 所以下面的声明是无效的,不会编译:

 List<Object> list = new ArrayList<String>(); // Will not compile. 

原因是,泛型是不变的。

强制类型检查:

在Java中引入泛型以在编译时强制执行类型检查。 同样,泛型类型在运行时由于类型擦除而没有任何类型信息。 所以,一个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]; 

上面的代码与上面解释的具有相同的含义。 如果你注意到,编译器会在那里给你一个Unchecked Cast Warning ,因为你正在类型化一个未知组件类型的数组。 这意味着,演员阵容可能在运行时失败。 例如,如果你有上述方法的代码:

 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; } 

因为该方法返回一个Object ,所以需要Typecast。 但是你可以肯定这是一个安全的演员。 所以,你甚至可以在这个变量上使用@SuppressWarnings。

问题是,当运行时泛型类型被删除,所以new E[10]将等同于new Object[10]

这将是危险的,因为它可能会排列其他数据比E型。 这就是为什么你需要明确说出你想要的类型

  • 创建Object数组并将其转换为E[]数组或
  • 使用Array.newInstance(Class componentType,int length)来创建在componentType argiment中传递的类型数组的真实实例。

这里是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)创建泛型数组Array.newInstance(Class, int)其中int是数组的大小。

检查:

 public Constructor(Class<E> c, int length) { elements = (E[]) Array.newInstance(c, length); } 

或未选中:

 public Constructor(int s) { elements = new Object[s]; }