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)
是不是要抛出任何异常?
不,它不应该,在运行时两个列表具有相同类型的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一书.
在您的代码示例中,
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