如何创建一个通用数组?
我不明白泛型和数组之间的联系。
我可以使用泛型类型创建数组引用:
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
。
但是,数组带有组件类型的运行时类型信息。 在运行时,数组使用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]; }