【问题标题】:Generics, Erasures and Bytecode泛型、擦除和字节码
【发布时间】:2017-05-05 20:28:27
【问题描述】:

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

【问题讨论】:

    标签: java generics


    【解决方案1】:

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

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

    查看更多detailed answer here

    编辑:一个小实验,看看它在实践中的作用。 作为对@Andy Turner 的回答的补充(信息量很大:它表明存在泛型类型信息),让我们看看运行时会发生什么。

    我们通过反射检查类结构,并用String 代替Integer 构建Foo<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)
    

    【讨论】:

      【解决方案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
      }
      

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

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

      在早期版本上编译的类文件与后来的 JVM 兼容,但反之则不行。

      【讨论】:

      • 谢谢。但这对我来说是新的:我认为生成的字节码无论如何都是一样的,而 Erasures 只是帮助维护了这一点。这是一个源代码的东西 - 编译器领域..,虽然不太确定.. .
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多