执行下面的代码时,代码执行完美而没有任何错误,但对于List<Integer>
类型的变量,get()
方法的返回类型应为Integer,但在执行此代码时,当我调用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));
}
但是在执行下面的代码时,只需将返回对象中的类检索添加到前一个代码块就会引发类强制转换异常。如果上面的代码执行完美,那么下面的代码也应该执行而没有任何异常:
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());
}
为什么java在获取对象的类类型时执行类型转换?
答案 0 :(得分:6)
编译器必须在必要时在字节代码级别插入类型检查指令,因此在分配给Object
时,例如Object o = x.get(0);
或System.out.println(x.get(0));
可能不需要它,调用x.get(0)
表达式上的方法需要它。
原因在于binary compatibility rules。简单地说,调用方法是由接收器类型继承还是显式声明是无关紧要的,表达式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
上的调用,如果该方法是已知的{ {1}} final
中声明的方法。这可能是因为这些特定方法are specified in the JLS并以此形式对它们进行编码,这使得JVM可以快速识别这些特殊方法。但java.lang.Object
指令和checkcast
指令的组合仍然表现出相同的兼容行为。
答案 1 :(得分:1)
这是因为PrintStream#println
:
public void println(Object x) {
String s = String.valueOf(x);
...
了解它如何将您提供给它的任何内容转换为字符串,但首先将其分配给Object
(因为Integer
是Object
而有效)。将您的第一个代码更改为:
ArrayList xa = new ArrayList();
xa.addAll(Arrays.asList("ASDASD", "B"));
List<Integer> x = xa;
Integer i = x.get(0);
System.out.println(i);
你会得到同样的失败。
修改强>
是的,迪迪埃的评论是正确的;经过一段时间的思考后更新。
这甚至可以简化为什么编译器插入额外的checkcast #5 // class java/lang/Integer
:
ArrayList<Integer> l = new ArrayList<>();
l.get(0).getClass();
在运行时,没有Integer
类型,只有普通Object
;这将编写其他东西:
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
检查我们从List
获得的类型实际上是Integer
。 List::get
是一种通用方法,运行时的泛型参数为Object
;要在运行时保持正确的List<Integer>
,需要checkcast
。