不应该此代码产生ClassCastException

Thi*_*thi 8 java generics classcastexception

以下代码编译并成功运行,没有任何异常

import java.util.ArrayList;

class SuperSample {}

class Sample extends SuperSample {

  @SuppressWarnings("unchecked")
  public static void main(String[] args) {

    try {

      ArrayList<Sample> sList = new ArrayList<Sample>();
      Object o = sList;
      ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;
      ssList.add(new SuperSample());

    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}
Run Code Online (Sandbox Code Playgroud)

不应该ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;生产线ClassCastException

虽然下面的代码产生编译时错误错误以防止堆污染,但上面提到的代码不应该在运行时进行类似的预防吗?

ArrayList<Sample> sList = new ArrayList<Sample>();
ArrayList<SuperSample> ssList = (ArrayList<SuperSample>) sList;
Run Code Online (Sandbox Code Playgroud)

编辑:

如果Type Erasure是这背后的原因,是否应该有其他机制来防止无效对象被添加到List?例如

String[] iArray = new String[5];
Object[] iObject = iArray;
iObject[0]= 5.5;  // throws ArrayStoreException
Run Code Online (Sandbox Code Playgroud)

那么为什么,

ssList.add(new SuperSample());
Run Code Online (Sandbox Code Playgroud)

是不是要抛出任何异常?

Mik*_*ail 6

不,它不应该,在运行时两个列表具有相同类型的ArrayList.这称为擦除.通用参数不是编译类的一部分,它们都在编译期间被擦除.从JVM的角度来看,您的代码等于:

public static void main(String[] args) {
    try {
      ArrayList sList = new ArrayList();
      Object o = sList;
      ArrayList ssList = (ArrayList)o;
      ssList.add(new SuperSample());
    } catch (Exception e) {
      e.printStackTrace();
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上泛型只通过生成编译时错误和警告来简化开发,但它们根本不会影响执行.

编辑:

那么,这背后的基本概念是Reifiable Type.我强烈建议您阅读本手册:

可重新类型是类型信息在运行时完全可用的类型.这包括基元,非泛型类型,原始类型和未绑定通配符的调用.

不可重新生成的类型是在编译时通过类型擦除删除信息的类型

简而言之:数组是可变的,而泛型集合则不是.因此,当您在数组中存储smth时,JVM会检查type,因为数组的类型在运行时存在.Array只代表一段memmory,而collection是一个普通的类,可能有任何类型的实现.例如,它可以将数据存储在db或引擎盖下的磁盘上.如果您想深入了解,我建议您阅读Java Generics and Collections一书.


Stu*_*rks 1

在您的代码示例中,

    class SuperSample { }
    class Sample extends SuperSample { }
    ...
    ArrayList<Sample> sList = new ArrayList<Sample>();
    Object o = sList;
    ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;
Run Code Online (Sandbox Code Playgroud)

最后一行不应该产生一个ClassCastException吗?

不会。当 JVM 检测到在运行时强制转换不兼容的类型时,就会引发该异常。正如其他人所指出的,这是因为泛型类型被删除了。也就是说,泛型类型只有编译器知道。在 JVM 级别,变量都是类型ArrayList(泛型已被删除),因此在运行时没有ClassCastException

顺便说一句,执行Object此赋值的更简洁方法是通过 raw 进行强制转换,而不是分配给类型为 的中间局部变量:

    ArrayList<SuperSample> ssList = (ArrayList)sList;
Run Code Online (Sandbox Code Playgroud)

其中“原始”类型是泛型类型的擦除版本。

难道不应该有额外的机制来防止无效对象被添加到列表中吗?

是的,有。第一个机制是编译时检查。在您自己的答案中,您在 Java 语言规范中找到了正确的位置,其中描述了堆污染,这是列表中出现的无效对象的术语。该部分的报价(位于底部)是

如果没有发生需要发出编译时未检查警告的操作,并且具有不可具体化元素类型的数组变量没有发生不安全别名,则不会发生堆污染。

因此,您正在寻找的机制位于编译器中,编译器会通过编译警告通知您这一点。但是,您已通过使用@SuppressWarnings注释禁用了此机制。如果您要删除此注释,则会在有问题的行处收到编译器警告。如果您绝对想防止堆污染,请不要使用@SuppressWarnings,并将选项添加-Xlint:unchecked -Werror到您的javac命令行中。

第二种机制是运行时检查,它需要使用已检查的包装器之一。将 的初始化替换sList为以下内容:

    List<Sample> sList = Collections.checkedList(new ArrayList<Sample>(), Sample.class);
Run Code Online (Sandbox Code Playgroud)

这将导致在 a添加到列表的ClassCastException位置抛出a 。SuperSample