原始类型

时间:2017-06-21 08:38:46

标签: java java-8 classcastexception raw-types unchecked-cast

执行下面的代码时,代码执行完美而没有任何错误,但对于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在获取对象的类类型时执行类型转换?

2 个答案:

答案 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(因为IntegerObject而有效)。将您的第一个代码更改为:

    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获得的类型实际上是IntegerList::get是一种通用方法,运行时的泛型参数为Object;要在运行时保持正确的List<Integer>,需要checkcast