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?
2 Answers
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.

- 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
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)