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

时间:2019-07-12 06:11:34

标签: java arrays generics syntax

第一个密码

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

为什么这是错误的?我只是遵循有效Java第三版第132页的方法:

第二个代码

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

但是我发现以下代码有效

第三代码

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

我的问题:

  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);
    }
}
  1. 有人可以解释为什么第三段代码正确但以下代码错误吗?

第四代码

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

4 个答案:

答案 0 :(得分:8)

第一个密码

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

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

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

值得注意的是,只有E不受限制时,此方法才有效。例如,此will fail在运行时带有ClassCastException

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

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

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

在使用代码时,请务必牢记擦除。否则,您可能会遇到代码行为与预期不同的情况。例如,以下代码compiles and runs without exceptions

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];

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

  

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

具有不是无界通配符(?)的通用参数的类型不满足任何condition for reifiability

  

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

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

答案 1 :(得分: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[](除非有限制,但这里不是这种情况),显然您可以将任何内容放入其中。 / p>

答案 2 :(得分:0)

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

答案 3 :(得分:0)

为什么第一个代码出现错误?

类型系统推断出有关程序的事实,例如,某个变量总是 包含一个整数列表。但是没有类型系统是完美的。总会有一些事实,程序员可以推断出,但是类型系统却不能。为了在这种情况下允许程序员变通,编译器会发出警告,而不是发出警告。 比执行某些强制转换时出错。

如何解析第一密码?

将对象列表转换为整数列表或任何其他类型的列表是非法的 因此,投放必须分两步进行。

  1. 首先,将对象列表转换为通配符类型列表;这个演员很安全。
  2. 第二,将通配符类型列表转换为整数列表
List<Integer>[] arrayTest_2 = (List<Integer>[])(List<?>[]) new Object[10]; 

程序员必须使用未经检查的强制类型转换,您必须执行第二,第三和第四代码。

学习愉快:)