猿问

关于在 Java 中创建通用列表数组的错误

第一个代码:


List<Integer>[] array = (List<Integer>[]) new Object[size]; 

它将给出以下异常:


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')


为什么这是错误的?我只是按照Effective Java Third Edition Page 132 的方法:


第二个代码:


E[] array = (E[]) new Object[size];

但是我发现以下代码有效


第三代码:


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

我的问题:


为什么第一个代码是错误的,但Effective 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);

    }

}

谁能解释为什么第三个代码是正确的但下面的代码是错误的?

第四个代码:


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


慕斯709654
浏览 202回答 3
3回答

饮歌长啸

第一码List<Integer>[]&nbsp;array&nbsp;=&nbsp;(List<Integer>[])&nbsp;new&nbsp;Object[size];第一个代码失败的原因是因为转换并没有改变数组的实际类型,它只是让编译器接受代码是有效的。想象一下,如果您有另一个对底层对象数组的引用:final int size = 2;Object[] objectArr = new Object[size];List<Integer>[] integerArr = (List<Integer>[]) objectArr; // Does not workobjectArr[0] = "foobar";List<Integer> i = integerArr[0]; // What would happen ??上面的代码编译得很好,因为你强制编译器接受它的强制转换。但是您已经可以看到为什么强制转换在运行时工作会是一个问题:您最终会得到一个List<Integer>[]现在包含一个 的String,这是没有意义的。所以语言不允许这样做。第二码E[]&nbsp;array&nbsp;=&nbsp;(E[])&nbsp;new&nbsp;Object[size];Java 中的泛型有点奇怪。由于各种原因,例如向后兼容性,泛型基本上被编译器擦除并且(大部分)不会出现在编译代码中(类型擦除)。相反,它将使用一系列规则(JLS 规范)来确定代码中应该使用哪种类型。对于基本的 unbouded 泛型;这种类型将是Object。因此,假设没有绑定E,编译器将第二个代码更改为:&nbsp;Object[]&nbsp;array&nbsp;=&nbsp;(Object[])&nbsp;new&nbsp;Object[size];因此,由于两个数组在擦除后具有完全相同的类型,因此在运行时没有问题,并且转换基本上是多余的。值得注意的是,这仅在E不受限制的情况下才有效。例如,这将在运行时失败并显示ClassCastException:public static <E extends Number> void genericMethod() {&nbsp; &nbsp; final int size = 5;&nbsp; &nbsp; E[] e = (E[]) new Object[size];}那是因为Ewill be erased to Number,你会遇到和第一个代码一样的问题:Number[] e = (Number[]) new Object[size];在使用代码时记住擦除很重要。否则,您可能会遇到代码行为与您预期不同的情况。例如,下面的代码编译和运行没有异常:public static <E> void genericMethod(E e) {&nbsp; &nbsp; final int size = 2;&nbsp; &nbsp; Object[] objectArr = new Object[size];&nbsp; &nbsp; objectArr[0] = "foobar";&nbsp; &nbsp; @SuppressWarnings("unchecked")&nbsp; &nbsp; E[] integerArr = (E[]) objectArr;&nbsp; &nbsp; integerArr[1] = e;&nbsp; &nbsp; System.out.println(Arrays.toString(integerArr));&nbsp; &nbsp; System.out.println(e.getClass().getName());&nbsp; &nbsp; System.out.println(integerArr.getClass().getName());}public static void main(String[] args) {&nbsp; &nbsp; genericMethod(new Integer(5)); // E is Integer in this case}第三码List<Integer>[]&nbsp;array&nbsp;=&nbsp;(List<Integer>[])&nbsp;new&nbsp;ArrayList[size];与上面的情况类似,第三个代码将被擦除为以下内容:&nbsp;List[]&nbsp;array&nbsp;=&nbsp;(List[])&nbsp;new&nbsp;ArrayList[size];这没问题,因为ArrayList是List.第四码List<Integer>[]&nbsp;array&nbsp;=&nbsp;new&nbsp;ArrayList<Integer>[size];以上不会编译。规范明确禁止使用具有泛型类型参数的类型创建数组:如果正在初始化的数组的组件类型不可具体化(第 4.7 节),则会出现编译时错误。具有不是无限通配符 (&nbsp;?) 的泛型参数的类型不满足可具体化的任何条件:当且仅当满足以下条件之一时,类型才可具体化:它指的是非泛型类或接口类型声明。它是一种参数化类型,其中所有类型参数都是无限通配符(第 4.5.1 节)。它是原始类型 (§4.8)。它是原始类型(§4.2)。它是一种数组类型 (§10.1),其元素类型是可具体化的。它是一个嵌套类型,其中对于由“.”分隔的每个类型 T,T 本身是可具体化的。

慕莱坞森

虽然我没有时间深入挖掘JLS,但我可以暗示您要看得更远(尽管每次我这样做,都不是一次愉快的旅行)。List<Integer>[]&nbsp;array&nbsp;=&nbsp;(List<Integer>[])&nbsp;new&nbsp;Object[size];这不会编译,因为它们是可证明的不同类型(搜索JLS这样的概念)。简而言之,编译器“能够”看到这些类型不可能与可能被强制转换的类型相同,因此失败。另一方面:array&nbsp;=&nbsp;(E[])&nbsp;new&nbsp;Object[10];这些不是可证明的不同类型;编译器无法判断这一定会失败。这里的另一件事是,编译器不会以任何形式或形状强制转换为泛型类型,您可以轻松地做这样的事情(仍然可以编译):String&nbsp;s[][][]&nbsp;=&nbsp;new&nbsp;String[1][2][3]; array&nbsp;=&nbsp;(E[])&nbsp;s;&nbsp;//&nbsp;this&nbsp;will&nbsp;compile,&nbsp;but&nbsp;makes&nbsp;little&nbsp;sense第二点是类型擦除(又有JLS)。编译代码后,E[]在运行时,是Object[](除非有界限,但这里不是这种情况),很明显你可以把任何你想要的东西放进去。

MM们

Java 中数组和泛型之间的交互是混乱的,因为它们建立在不同的假设之上。Java 数组有运行时类型检查,泛型只有编译时类型检查。Java 通过结合编译时和运行时检查来实现类型安全。转换绕过了大部分编译时检查,但仍然有运行时检查。数组与其包含的元素类型具有本质上相同的类型兼容性规则。所以:Object[] a = new String[size]; //ok, but be aware of the potential for an ArrayStoreExceptionString[] a = new Object[size]; //compile errorString[] a = (String[]) new Object[size]; //runtime error当 Sun 决定将泛型添加到 Java 时,他们决定使用泛型的代码应该在现有的 JVM 上运行,因此他们决定通过擦除来实现泛型。泛型类型只存在于编译时,在运行时它们被普通类型取代。所以在擦除之前我们有以下语句。List<Integer>[] array = (List<Integer>[]) new Object[size];E[] array = (E[]) new Object[size];List<Integer>[] array = (List<Integer>[]) new List[size];擦除后我们有。List[] array = (List[]) new Object[size]; //run time error.Object[] array = (Object[]) new Object[size]; //no error.List[] array = (List[]) new List[size]; //no error.E[] array = (E[]) new Object[size];应谨慎使用该结构,它违反了 Java 的正常类型模型,如果数组返回到非泛型上下文,将导致令人困惑的 ClassCastException 。不幸的是,通常没有更好的选择,因为类型擦除,泛型类型无法找出它的元素类型并构造正确类型的数组。
随时随地看视频慕课网APP

相关分类

Java
我要回答