如何创建通用数组?

use*_*979 84 java generics

我不理解泛型和数组之间的联系.

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

private E[] elements; //GOOD
Run Code Online (Sandbox Code Playgroud)

但无法使用泛型类型创建数组对象:

elements = new E[10]; //ERROR
Run Code Online (Sandbox Code Playgroud)

但它有效:

elements = (E[]) new Object[10]; //GOOD
Run Code Online (Sandbox Code Playgroud)

Roh*_*ain 190

你不应该混淆数组和泛型.他们并不顺利.数组和泛型类型如何强制执行类型检查存在差异.我们说阵列是有效的,但泛型不是.因此,您会发现这些差异适用于数组和泛型.

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

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

Object[] arr = new String[10];
Run Code Online (Sandbox Code Playgroud)

基本上,a Object[]是超类型String[],因为Object是超类型String.泛型不是这样.因此,以下声明无效,并且不会编译:

List<Object> list = new ArrayList<String>(); // Will not compile.
Run Code Online (Sandbox Code Playgroud)

原因是,仿制药是不变的.

执行类型检查:

在Java中引入了泛型,以便在编译时强制执行更强大的类型检查.因此,由于类型擦除,泛型类型在运行时没有任何类型信息.因此,a List<String>具有静态类型List<String>但是动态类型List.

但是,数组随身携带组件类型的运行时类型信息.在运行时,数组使用Array Store检查来检查是否插入与实际数组类型兼容的元素.那么,以下代码:

Object[] arr = new String[10];
arr[0] = new Integer(10);
Run Code Online (Sandbox Code Playgroud)

将编译正常,但由于ArrayStoreCheck将在运行时失败.使用泛型,这是不可能的,因为编译器将通过提供编译时检查来避免运行时异常,避免创建这样的引用,如上所示.

那么,通用阵列创建的问题是什么?

创建其组件类型是类型参数,具体参数化类型或有界通配符参数化类型的数组是类型不安全的.

考虑以下代码:

public <T> T[] getArray(int size) {
    T[] arr = new T[size];  // Suppose this was allowed for the time being.
    return arr;
}
Run Code Online (Sandbox Code Playgroud)

由于T在运行时未知类型,因此创建的数组实际上是一个Object[].所以上面的方法在运行时看起来像:

public Object[] getArray(int size) {
    Object[] arr = new Object[size];
    return arr;
}
Run Code Online (Sandbox Code Playgroud)

现在,假设您将此方法称为:

Integer[] arr = getArray(10);
Run Code Online (Sandbox Code Playgroud)

这是问题所在.您刚刚分配了Object[]一个参考Integer[].上面的代码编译正常,但在运行时会失败.

这就是为什么禁止通用数组创建的原因.

为什么类型转换new Object[10]E[]作品?

现在你最后的疑问,为什么下面的代码工作:

E[] elements = (E[]) new Object[10];
Run Code Online (Sandbox Code Playgroud)

上述代码具有与上述相同的含义.如果您注意到,编译器会在那里为您提供未经检查的强制转换警告,因为您要对未知组件类型的数组进行类型转换.这意味着,强制转换可能会在运行时失败.例如,如果您在上述方法中有该代码:

public <T> T[] getArray(int size) {
    T[] arr = (T[])new Object[size];        
    return arr;
}
Run Code Online (Sandbox Code Playgroud)

你打电话调用它像这样:

String[] arr = getArray(10);
Run Code Online (Sandbox Code Playgroud)

这将在运行时因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.
Run Code Online (Sandbox Code Playgroud)

现在,上述情况下的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
Run Code Online (Sandbox Code Playgroud)

以上两种情况都很好,因为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;
}
Run Code Online (Sandbox Code Playgroud)

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