Java 泛型、擦除和字节码

Java 泛型、擦除和字节码,java,generics,Java,Generics,我是Java泛型新手,我一直很难弄清楚它的内部工作原理。如果编译器执行的擦除删除了.java文件中的所有类型参数,并生成了一个旧JVM可以理解的普通.class文件,那么我们如何能够从其他类中引用这样一个类,知道java编译器在引用程序中的其他类时使用的是.class文件吗?编译器如何处理该.class文件中的所有对象引用,以确定哪个是原始对象,哪个是擦除的结果?类和方法签名中的泛型以及成员变量不会被擦除 一个简单的类: class Foo<T> { T field; vo

我是Java泛型新手,我一直很难弄清楚它的内部工作原理。如果编译器执行的擦除删除了.java文件中的所有类型参数,并生成了一个旧JVM可以理解的普通.class文件,那么我们如何能够从其他类中引用这样一个类,知道java编译器在引用程序中的其他类时使用的是.class文件吗?编译器如何处理该.class文件中的所有对象引用,以确定哪个是原始对象,哪个是擦除的结果?

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

一个简单的类:

class Foo<T> {
  T field;

  void bar(List<T> list) {
    T obj = list.get(0);
    T zip = field;
  }
}
class-Foo{
T场;
空栏(列表){
T obj=list.get(0);
T zip=字段;
}
}
反编译:

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 Foo{//在这里仍然有。
T field;//这里仍然有T。
Foo();
代码:
0:aload_0
1:invokespecial#1//方法java/lang/Object。“:()V
4:返回
void bar(java.util.List);//这里仍然有。
代码:
0:aload_1
1:iconst_0
//但是T在方法体中被擦除了。
2:invokeinterface#2,2//InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
7:astore_2
8:aload_0
//在引用字段时,T已被擦除。
9:getfield#3//Field:Ljava/lang/Object;
12:astore_3
13:返回
}

生成旧JVM可以理解的普通.class文件

事实并非如此:如果编译使用泛型的代码,则不支持泛型的JVM无法理解它


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

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

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

看更多

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

我们通过反射检查类结构,并用
字符串
代替
整数构建
Foo

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() + ")");
    }
}
import java.lang.reflect.Field;
导入java.lang.reflect.Method;
导入java.util.array;
导入java.util.List;
福班{
T场;
空栏(列表){
T obj=list.get(0);
T zip=字段;
}
公共静态void main(字符串[]args)抛出ReflectiveOperationException{
字段字段=Foo.class.getDeclaredField(“字段”);
System.out.println(“字段:”
+“\n-”+字段。getType()
+“\n-”+字段。getGenericType()
+“\n-”+字段。getAnnotatedType()
);
Method=Foo.class.getDeclaredMethod(“bar”,List.class);
System.out.println(“方法:”
+“\n-”+Arrays.toString(method.getParameterTypes())
+“\n-”+Arrays.toString(method.getGenericParameterTypes())
);
Foo-Foo=新的Foo();

//foo.field=“hi”谢谢。但这对我来说是新鲜事:我认为不管怎样,生成的字节码都是一样的,擦除只是帮助保持了这一点。这是源代码的事情-编译器领域..,但不太确定。
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)