为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕斯709654 2023-03-23 15:17:32
第一个代码: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];
查看完整描述

3 回答

?
饮歌长啸

TA贡献1951条经验 获得超3个赞

第一码

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

第一个代码失败的原因是因为转换并没有改变数组的实际类型,它只是让编译器接受代码是有效的。想象一下,如果您有另一个对底层对象数组的引用:

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 ??

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

第二码

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

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

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

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

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

public static <E extends Number> void genericMethod() {

    final int size = 5;

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

}

那是因为Ewill be erased to Number,你会遇到和第一个代码一样的问题:


Number[] e = (Number[]) new Object[size];

在使用代码时记住擦除很重要。否则,您可能会遇到代码行为与您预期不同的情况。例如,下面的代码编译和运行没有异常:


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

}

第三码

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

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

 List[] array = (List[]) new ArrayList[size];

这没问题,因为ArrayListList.

第四码

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

以上不会编译。规范明确禁止使用具有泛型类型参数的类型创建数组:

如果正在初始化的数组的组件类型不可具体化(第 4.7 节),则会出现编译时错误。

具有不是无限通配符 ( ?) 的泛型参数的类型不满足可具体化的任何条件

当且仅当满足以下条件之一时,类型才可具体化:

  • 它指的是非泛型类或接口类型声明。

  • 它是一种参数化类型,其中所有类型参数都是无限通配符(第 4.5.1 节)。

  • 它是原始类型 (§4.8)。

  • 它是原始类型(§4.2)。

  • 它是一种数组类型 (§10.1),其元素类型是可具体化的。

  • 它是一个嵌套类型,其中对于由“.”分隔的每个类型 T,T 本身是可具体化的。


查看完整回答
反对 回复 2023-03-23
?
慕莱坞森

TA贡献1810条经验 获得超4个赞

虽然我没有时间深入挖掘JLS,但我可以暗示您要看得更远(尽管每次我这样做,都不是一次愉快的旅行)。

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

这不会编译,因为它们是可证明的不同类型(搜索JLS这样的概念)。简而言之,编译器“能够”看到这些类型不可能与可能被强制转换的类型相同,因此失败。

另一方面:

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

这些不是可证明的不同类型;编译器无法判断这一定会失败。这里的另一件事是,编译器不会以任何形式或形状强制转换为泛型类型,您可以轻松地做这样的事情(仍然可以编译):

String s[][][] = new String[1][2][3];
array = (E[]) s; // this will compile, but makes little sense

第二点是类型擦除(又有JLS)。

编译代码后,E[]在运行时,是Object[](除非有界限,但这里不是这种情况),很明显你可以把任何你想要的东西放进去。


查看完整回答
反对 回复 2023-03-23
?
MM们

TA贡献1886条经验 获得超2个赞

Java 中数组和泛型之间的交互是混乱的,因为它们建立在不同的假设之上。Java 数组有运行时类型检查,泛型只有编译时类型检查。


Java 通过结合编译时和运行时检查来实现类型安全。转换绕过了大部分编译时检查,但仍然有运行时检查。数组与其包含的元素类型具有本质上相同的类型兼容性规则。所以:


Object[] a = new String[size]; //ok, but be aware of the potential for an ArrayStoreException

String[] a = new Object[size]; //compile error

String[] 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 。不幸的是,通常没有更好的选择,因为类型擦除,泛型类型无法找出它的元素类型并构造正确类型的数组。


查看完整回答
反对 回复 2023-03-23
  • 3 回答
  • 0 关注
  • 117 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信