关于在Java中创建泛型列表数组的错误

map*_*ple 12 java arrays generics syntax

第一个代码

List<Integer>[] array = (List<Integer>[]) new Object[size]; 
Run Code Online (Sandbox Code Playgroud)

它将给出以下异常:

java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.util.List; ([Ljava.lang.Object; and [Ljava.util.List; are in module java.base of loader 'bootstrap')

为什么会这样呢?我只是按照有效Java第三版 132页的方法进行操作:

第二码

E[] array = (E[]) new Object[size];
Run Code Online (Sandbox Code Playgroud)

但是我发现以下代码有效

第三码

List<Integer>[] array = (List<Integer>[]) new List[size];
Run Code Online (Sandbox Code Playgroud)

我的问题:

  1. 为什么第一个代码是错误的,但是在有效Java中建议使用第二个代码?我有误会吗?

例如:为什么以下代码运行良好,但是第一个代码错误?

public class Test<E>{
    E[] array;
    public Test(){
        array = (E[]) new Object[10];
    }
    public E set(E x){
        array[0] = x;
        System.out.println(array[0]);
        return array[0];
    }

    public static void main(String[] args){
        Test<List<Integer>> test = new Test<>();
        List<Integer> list = new ArrayList<>();
        list.add(1);
        test.set(list);
    }
}
Run Code Online (Sandbox Code Playgroud)
  1. 谁能解释为什么第三个代码正确但以下代码错误?

第四码

List<Integer>[] array = new List<Integer>[size];

Tii*_*iJ7 9

第一码

List<Integer>[] array = (List<Integer>[]) new Object[size]; 
Run Code Online (Sandbox Code Playgroud)

第一个代码失败的原因是因为强制转换不会更改数组的实际类型,它只是使编译器接受该代码为有效代码。想象一下,如果您还有对底层对象数组的引用:

final int size = 2;
Object[] objectArr = new Object[size];
List<Integer>[] integerArr = (List<Integer>[]) objectArr; // Does not work
objectArr[0] = "foobar";
List<Integer> i = integerArr[0]; // What would happen ??
Run Code Online (Sandbox Code Playgroud)

上面的代码可以很好地编译,因为您正在强制编译器通过强制转换接受它。但是您已经知道为什么强制转换在运行时工作会成为问题:您最终会得到一个List<Integer>[]现在包含的String,这没有任何意义。因此该语言不允许这样做。

第二码

E[] array = (E[]) new Object[size];
Run Code Online (Sandbox Code Playgroud)

Java中的泛型有点奇怪。由于各种原因(例如向后兼容),泛型基本上会被编译器擦除,并且(通常)不会出现在已编译的代码中(Type Erasure)。相反,它将使用一系列规则JLS spec)来确定应在代码中使用哪种类型。对于基本的无绑定通用名;这种类型将是Object。因此,假设没有限制E,编译器将第二个代码更改为:

 Object[] array = (Object[]) new Object[size];
Run Code Online (Sandbox Code Playgroud)

因此,由于擦除后两个数组的类型完全相同,因此在运行时没有问题,并且强制转换基本上是多余的。

值得注意的是,这只有E在不受限制的情况下才有效。例如,这在运行时失败,并带有ClassCastException

public static <E extends Number> void genericMethod() {
    final int size = 5;
    E[] e = (E[]) new Object[size];
}
Run Code Online (Sandbox Code Playgroud)

那是因为E将被擦除为Number,您将得到与第一个代码相同的问题:

Number[] e = (Number[]) new Object[size];
Run Code Online (Sandbox Code Playgroud)

在使用代码时,请务必谨记擦除。否则,您可能会遇到代码行为与预期不同的情况。例如,以下代码无例外地编译和运行

public static <E> void genericMethod(E e) {
    final int size = 2;
    Object[] objectArr = new Object[size];
    objectArr[0] = "foobar";

    @SuppressWarnings("unchecked")
    E[] integerArr = (E[]) objectArr;
    integerArr[1] = e;

    System.out.println(Arrays.toString(integerArr));
    System.out.println(e.getClass().getName());
    System.out.println(integerArr.getClass().getName());
}

public static void main(String[] args) {
    genericMethod(new Integer(5)); // E is Integer in this case
}
Run Code Online (Sandbox Code Playgroud)

第三码

List<Integer>[] array = (List<Integer>[]) new ArrayList[size];
Run Code Online (Sandbox Code Playgroud)

与上述情况类似,第三个代码将被擦除为以下内容:

 List[] array = (List[]) new ArrayList[size];
Run Code Online (Sandbox Code Playgroud)

没问题,因为它ArrayList是的子类型List

第四码

List<Integer>[] array = new ArrayList<Integer>[size];
Run Code Online (Sandbox Code Playgroud)

以上内容将无法编译。spec明确禁止使用类型具有泛型类型参数的数组进行创建:

如果正在初始化的数组的组件类型不可修改(第4.7节),则是编译时错误。

具有非无限通配符(?)的通用参数的类型不满足任何可验证性条件

仅当以下条件之一成立时,类型才是可更改的:

  • 它指的是非泛型类或接口类型声明。
  • 它是一个参数化类型,其中所有类型参数都是无界通配符(第4.5.1节)。
  • 这是原始类型(第4.8节)。
  • 它是原始类型(第4.2节)。
  • 它是一种数组类型(第10.1节),其元素类型是可修改的。
  • 它是一个嵌套类型,其中对于每个用“。”分隔的类型T,T本身都是可确定的。