3

I'm new to Java Generics, and I've been having trouble figuring out its inner workings. If Erasures performed by the compiler remove all the type-parameters there are in a .java file and produce a normal .class file that can be understood by older JVMs, then how is it we're able to reference such a class generically from other classes, knowing it's the .class file that the java compiler works with when we reference other classes from our program? How does the compiler handle all Object references in that .class file in terms of deciding which is originally Object and which is the result of Erasure?

Searcherer
  • 111
  • 2
  • 10

2 Answers2

2

The generics in class and method signatures, and member variables, aren't erased.

A 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
}

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

This isn't the case: if you compile code which uses generics, it can't be understood by JVMs which don't support generics.

Class files compiled on earlier versions are compatible with later JVMs, but not the other way around.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Thanks. But that's new to me: I thought the resulting bytecode was the same regardless, and Erasures just helped maintain that. It is a source code thing - compiler territory.. , not quite sure though.. . – Searcherer May 05 '17 at 10:49
2

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

Compiler uses that info at compile time, but the JVM does not use it at runtime. That info is accessible through reflection, and some libraries use it (Hibernate does that).

See a more detailed answer here

Edit: a small experiment to see it play in practice. In complement to @Andy Turner's answer (which is very informative: it shows that the generic type info is there), let's see what happens at runtime.

We inspect the class structure via reflection, and build a Foo<Integer> with a String in place of the 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)
Community
  • 1
  • 1
Hugues M.
  • 19,846
  • 6
  • 37
  • 65