Java 为原始类型引发的ClassCastException不一致
当执行下面的代码时,代码执行得非常完美,没有任何错误,但是对于类型为Java 为原始类型引发的ClassCastException不一致,java,java-8,classcastexception,raw-types,unchecked-cast,Java,Java 8,Classcastexception,Raw Types,Unchecked Cast,当执行下面的代码时,代码执行得非常完美,没有任何错误,但是对于类型为List的变量,get()方法的返回类型应该是整数,但是在执行此代码时,当我调用x.get(0)时,返回一个字符串,而这应该引发异常 public static void main(String[] args) { ArrayList xa = new ArrayList(); xa.addAll(Arrays.asList("ASDASD", "B"));
List
的变量,get()
方法的返回类型应该是整数,但是在执行此代码时,当我调用x.get(0)
时,返回一个字符串,而这应该引发异常
public static void main(String[] args)
{
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
System.out.println(x.get(0));
}
publicstaticvoidmain(字符串[]args)
{
ArrayList xa=新的ArrayList();
addAll(Arrays.asList(“ASDASD”,“B”));
列表x=xa;
系统输出println(x.get(0));
}
但是,在执行下面的代码时,仅将类从返回对象的检索添加到上一个代码块就会引发类强制转换异常。如果上述代码完全执行,则以下代码也应毫无例外地执行:
public static void main(String[] args)
{
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
System.out.println(x.get(0).getClass());
}
publicstaticvoidmain(字符串[]args)
{
ArrayList xa=新的ArrayList();
addAll(Arrays.asList(“ASDASD”,“B”));
列表x=xa;
System.out.println(x.get(0.getClass());
}
为什么java在获取对象的类类型时执行类型转换?这是因为
PrintStream\println
:
public void println(Object x) {
String s = String.valueOf(x);
...
了解它如何将您提供的任何内容转换为字符串,但首先将其分配给对象
(这是因为整数
是对象
)。将第一个代码更改为:
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
Integer i = x.get(0);
System.out.println(i);
在运行时,没有整数
类型,只有普通的对象
;其中包括:
10: invokevirtual #4 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
13: checkcast #5 // class java/lang/Integer
16: invokevirtual #6 // Method java/lang/Object.getClass:()Ljava/lang/Class;
注意,
checkcast
检查我们从列表中得到的类型实际上是一个整数<代码>列表::get
是一个通用方法,运行时该通用参数将是一个对象
;要在运行时保持正确的列表
,需要执行checkcast
编译器必须在必要时在字节码级别插入类型检查指令,以便在分配给对象时,例如对象o=x.get(0)
orSystem.out.println(x.get(0))代码>,可能不需要它,调用表达式x上的方法。get(0)
确实需要它
原因就在于这一点。简单地说,被调用的方法是由接收方类型继承还是显式声明是不相关的,表达式x.get(0)
的形式类型是Integer
,并且您正在对其调用方法getClass()
,因此,调用将被编码为一个名为getClass
的方法调用,其签名为()→接收方类上的java.lang.Class
java.lang.Integer
。此方法是从java.lang.Object
继承的,并且在编译时声明为final
,编译类没有反映这一事实
因此从理论上讲,在运行时,可以从java.lang.Object
中删除该方法,并在java.lang.Class getClass()
中添加一个新方法java.lang.Integer
,而不会破坏与该特定代码的兼容性。虽然我们知道这永远不会发生,但编译器只是遵循正式规则,不向代码中注入关于继承的假设
由于调用将被编译为以java.lang.Integer
为目标的调用,因此在调用指令之前需要进行类型转换,这在堆污染场景中会失败
请注意,如果将代码更改为
System.out.println(((Object)x.get(0)).getClass());
您将明确假设该方法已在java.lang.Object
中声明。扩展到java.lang.Object
将不会生成任何额外的字节码指令,所有这些代码都会将方法调用的接收器类型更改为java.lang.Object
,从而消除类型转换的需要
这里的规则有一个有趣的偏差,编译器确实将调用编码为字节码级别的java.lang.Object
调用,如果该方法是java.lang.Object
中声明的已知final
方法之一。这可能是因为这些特定方法和以这种形式编码它们允许JVM快速识别这些特殊方法。但是,checkcast
指令和invokevirtual
指令的组合仍然表现出相同的兼容行为。这并不能真正解释为什么编译器在调用getClass()
@didiierl时会生成一个cast。引用类型是List
;对我来说似乎很有逻辑,我希望在赋值给Integer
变量(如您的示例中)、调用属于Integer
的方法或将其传递给以Integer
为参数的方法时使用强制转换。如果没有霍尔格的解释,我就不会期望调用在对象
中声明的方法(如getClass()
)时使用强制转换。关于分解输出,有趣的一点是执行时不需要使用checkcast
指令。这让我再次思考,所以我只是验证了形式规则的应用对于任何其他类仍然有效,调用将使用确切的接收方类型进行编码,这使得checkcast
是必要的。因此,调用指令中对java.lang.Object
方法的特殊处理不应影响其他指令,最值得注意的是,不允许省略如此有趣的checkcast
…@Holger!事实上,你可以在这里基本上扔掉checkcast
;对我来说,这看起来像是一个快速失败的场景……添加的校验不是因为类型擦除,不是因为二进制兼容性吗?@Eugene:这是c
System.out.println(((Object)x.get(0)).getClass());