4

执行以下代码时,代码执行完美,没有任何错误,但是对于 type 的变量,方法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在获取对象的类类型时会执行类型转换?

4

2 回答 2

6

编译器必须在必要时在字节码级别插入类型检查指令,因此虽然对或的Object赋值可能不需要它,但在表达式上调用方法确实需要它。Object o = x.get(0);System.out.println(x.get(0));x.get(0)

原因在于二进制兼容性规则。简单地说,被调用的方法是否被接收者类型继承或显式声明是无关紧要的,表达式的形式类型x.get(0)Integer并且你正在调用getClass()它上面的方法,因此,调用将被编码为方法的调用用接收器类上getClass的签名命名。该方法已被继承并在编译时声明的事实不会被编译的类反映。() → java.lang.Classjava.lang.Integerjava.lang.Objectfinal

所以理论上,在运行时,可以删除该方法并添加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方法是finaljava.lang.Object. 这可能是因为这些特定方法是在 JLS 中指定的,并且以这种形式对它们进行编码允许 JVM 快速识别这些特殊方法。但是checkcast指令和invokevirtual指令的组合仍然表现出相同的、兼容的行为。

于 2017-06-21T10:14:51.540 回答
2

这是因为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是需要的。

于 2017-06-21T08:59:42.603 回答