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)
or
System.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());