泛型,擦除和字节码

时间:2017-05-05 07:34:17

标签: java generics

我是Java Generics的新手,我一直无法弄清楚其内部工作原理。如果编译器执行 Erasures ,则删除.java文件中的所有类型参数,并生成一个可以被旧JVM理解的普通.class文件,那么我们怎么做呢?能够从其他类中一般地引用这样的类,知道它是我们从程序中引用其他类时java编译器使用的.class文件吗?编译器如何处理该.class文件中的所有 Object 引用,以确定哪个是最初的Object,哪个是Erasure的结果?

2 个答案:

答案 0 :(得分:2)

类和方法签名中的泛型以及成员变量都不会被删除。

一个简单的课程:

class Foo<T> {
  T field;

  void bar(List<T> list) {
    T obj = list.get(0);
    T zip = field;
  }
}

反编译:

class Foo<T> {  // Still got the <T> here.
  T field;  // Still got the T here.

  Foo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  void bar(java.util.List<T>);  // Still got the <T> here.
    Code:
       0: aload_1
       1: iconst_0
       // But T has been erased inside the method body.
       2: invokeinterface #2,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
       7: astore_2
       8: aload_0
       // And T has been erased when referencing field.
       9: getfield      #3                  // Field field:Ljava/lang/Object;
      12: astore_3
      13: return
}
  

生成一个普通的.class文件,可以被较旧的JVM理解

情况并非如此:如果您编译使用泛型的代码,那么不支持泛型的JVM就无法理解它。

在早期版本上编译的类文件与以后的JVM兼容,但不是相反。

答案 1 :(得分:2)

简而言之,类型声明,方法签名等中的泛型及其约束的详细信息仍然在字节码中编码为元数据。

编译器在编译时使用该信息,但JVM在运行时不使用它。这些信息可以通过反射访问,有些库使用它(Hibernate就是这样)。

查看更多detailed answer here

编辑:一个小实验,看它在实践中发挥作用。 作为@Andy Turner答案的补充(这是非常有用的:它显示了通用类型信息),让我们看看运行时会发生什么。

我们通过反射检查类结构,并使用Foo<Integer>构建String代替Integer

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

class Foo<T> {
    T field;

    void bar(List<T> list) {
        T obj = list.get(0);
        T zip = field;
    }

    public static void main(String[] args) throws ReflectiveOperationException {
        Field field = Foo.class.getDeclaredField("field");
        System.out.println("Field:"
                + "\n - " + field.getType()
                + "\n - " + field.getGenericType()
                + "\n - " + field.getAnnotatedType()
        );
        Method method = Foo.class.getDeclaredMethod("bar", List.class);
        System.out.println("Method:"
                + "\n - " + Arrays.toString(method.getParameterTypes())
                + "\n - " + Arrays.toString(method.getGenericParameterTypes())
        );
        Foo<Integer> foo = new Foo<>();
        // foo.field = "hi"; <- Compile error, incompatible types
        field.set(foo, "hi"); //
        // Integer value = foo.field; <- Accepted by compiler, fails at runtime with ClassCastException
        Object value = foo.field; // OK
        System.out.println("Value of field: " + value + " (class: " + value.getClass() + ")");
    }
}

结果:

Field:
 - class java.lang.Object
 - T
 - sun.reflect.annotation.AnnotatedTypeFactory$AnnotatedTypeVariableImpl@5a2e4553
Method:
 - [interface java.util.List]
 - [java.util.List<T>]
Value of field: hi (class: class java.lang.String)