不应该此代码产生ClassCastException

时间:2013-11-22 11:16:09

标签: 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();
    }

  }
}

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

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

ArrayList<Sample> sList = new ArrayList<Sample>();
ArrayList<SuperSample> ssList = (ArrayList<SuperSample>) sList;

修改

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

String[] iArray = new String[5];
Object[] iObject = iArray;
iObject[0]= 5.5;  // throws ArrayStoreException

然后为什么,

ssList.add(new SuperSample());

是不是要抛出任何异常?

5 个答案:

答案 0 :(得分:6)

不,不应该,在运行时两个列表都具有相同的类型ArrayList。这称为erasure。通用参数不是编译类的一部分,它们都在编译期间被擦除。从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();
    }
}

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

修改

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

  

可再生类型是类型信息完全可用的类型   在运行时。这包括原语,非泛型类型,原始类型,   和未绑定的通配符的调用。

     

不可再生类型是已删除信息的类型   按类型擦除编译时间

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

答案 1 :(得分:1)

回答你问题的关键是java中的Type Erasure

你在第一个案例的编译时有一个警告而在第二个案例中没有警告,因为你的对象间接阻止了编译器向你发出警告(我猜测这个警告当将参数化类型转换为另一个未在第二个案例中完成的类型时,如果有人可以确认我很高兴在这里讨论它,则会引发此问题。 并且您的代码会运行,因为最终sList ssList et o都是ArrayList

答案 2 :(得分:1)

我认为由于Java中的向后兼容性问题,这不能产生ClassCastException。

通用信息不包含在字节码中(编译器在编译期间编译它)。

想象一下,您在项目中使用了一些旧的遗留代码(在Java 1.4中编写了一些旧库),并将泛型List传递给此遗留代码中的某些方法。 你可以这样做。

在允许泛型遗留代码将任何内容(除了基元之外)放入集合之前的时间。 因此,即使它尝试将String放入List&lt; Integer&gt;,这个遗留代码也无法获得ClassCastException。 从遗留代码的角度来看,它只是List。

因此,这种奇怪的行为是类型擦除的结果,并允许在Java中向后兼容。

修改

你得到数组的ArrayStoreException,因为在运行时JVM知道数组的类型,并且由于类型擦除而没有得到集合的任何异常,并且这种向后兼容性问题JVM在运行时不知道集合的类型。

您可以在第7章“泛型和集合”中的“SCJPSun®认证程序员Java™6学习指南”一书中阅读有关此主题的内容

答案 3 :(得分:1)

在您的代码示例中,

    class SuperSample { }
    class Sample extends SuperSample { }
    ...
    ArrayList<Sample> sList = new ArrayList<Sample>();
    Object o = sList;
    ArrayList<SuperSample> ssList = (ArrayList<SuperSample>)o;
  

最后一行不应产生ClassCastException吗?

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

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

    ArrayList<SuperSample> ssList = (ArrayList)sList;

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

  

是否应该有其他机制来防止将无效对象添加到列表中?

是的,有。第一种机制是编译时检查。在own answer中,您在Java语言规范中找到了正确的位置,其中描述了heap pollution,这是列表中出现的无效对象的术语。该部分的报价从底部开始,是

  

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

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

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

    List<Sample> sList = Collections.checkedList(new ArrayList<Sample>(), Sample.class);

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

答案 4 :(得分:0)

来自JLS (4.12.2)

参数化类型的变量可能是指不是的对象 该参数化类型。这种情况称为堆污染。这个情况 只有在程序执行了一些会产生的操作时才会发生 在编译时发出未经检查的警告。

例如,代码:

List l = new ArrayList<Number>();
List<String> ls = l; // unchecked warning

引起了一个未经检查的警告,因为在编译时无法确定 时间(在编译时类型检查规则的限制内)或在运行时,是否 变量l确实引用了List<String>。 如果执行上面的代码,则会产生堆污染,因为变量ls被声明为a List<String>,指的是实际上不是List<String>的值。 在运行时无法识别问题,因为类型变量未实现, 因此,实例在运行时不会携带有关实际类型的任何信息 用于创建它们的参数。