Generics, erasures and bytecode

I am new to Java Generics and am having trouble figuring out its inner workings. If the Erasures executed by the compiler remove all type parameters in the .java file and create a regular .class file that can be understood by older JVMs, then how can we refer to such a class in general from other classes, knowing that it is a file. class that the java compiler works with when we reference other classes from our program? How does the compiler handle all Object references in this .class file in terms of defining which is originally an Object and which is the result of Erasure?

+3


source to share


2 answers


In short, details about generics and their limitations in type declarations, method signatures, etc. are still encoded as bytecode metadata.

The compiler uses this information at compile time, but the JVM does not use it at run time. This information is available through reflection, and some libraries use it (Hibernate does it).

See here for details

Edit: a little experiment to see how it plays in practice. In addition to @ Andy Turner's answer (which is very informative: it shows that there is generic type information), let's see what happens at runtime.



We check the structure of the class through reflection and build Foo<Integer>

with String

instead of 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() + ")");
    }
}

      

Result:

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


source


Generics in class and method signatures and member variables are not removed.

Simple class:

class Foo<T> {
  T field;

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

      

decompiled:

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
}

      




creates a normal .class file that can be understood by older JVMs

This is not the case: if you compile code that uses generics, it cannot be understood by JVMs that do not support generics.

Class files compiled in earlier versions are compatible with later JVMs, but not vice versa.

+2


source







All Articles