views:

153

answers:

4

My obviously wrong understanding of Java Generics was up to now, that Type Erasure removes all type information such that there is nothing left at all at runtime. Recently i stumbled upon a code fragment where i had to ask myself: How the hack does this work? Simplified it presents as

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public abstract class SuperClass<T> {


    private final Type type;

    protected SuperClass(){
        ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
        type = parameterizedType.getActualTypeArguments()[0];
    }

    public void tellMyType(){
        System.out.println("Hi, my type parameter is " + type);
    }    
}

and

public class Example {

    public static void main(String[] args) {
        SuperClass sc = new SuperClass<Integer>(){};
        sc.tellMyType();
    }
}

Executing the Main Class results in Hi, my type parameter is class java.lang.Integer.

What we can see here is, that the type information of T is also available at runtime, which contradicts my initial understanding.

So my question is: Why does the compiler keep this? Is this required for some internal JVM behavior or is there any reasonable explanation for this effect?

Edit: - Fixed < and >

A: 

This FAQ on type erasure might help.

matt b
Did you check whether it does?
danben
How can I know if it helps Lars?
matt b
+5  A: 

Type parameters are being erased only from the dynamic types (i.e. a type of object being created):

Object o = new ArrayList<String>(); // String erased

It's retained in static types (i.e. field, argument and return types, throws clause, superclass and superinterface declarations):

class Test implements Superclass<String> { // String retained 
    // Accessible via Class.getGenericSuperclass()

    private List<Integer> l; // Integer retained (via Field.getGenericType())

    public void test(List<Long> l) {} // Long retained (via Method.getGenericParameterTypes())

    // Character retained (via Method.getGenericReturnType())
    public List<Character> test() { return null; }
}

In your case, you create an anonymous subclass of SuperClass<Integer>, so type parameter is retained in the superclass declaration.

axtavt
+3  A: 

From http://www.artima.com/weblogs/viewpost.jsp?thread=208860:

It turns out that while the JVM will not track the actual type arguments for instances of a generic class, it does track the actual type arguments for subclasses of generic classes. In other words, while a new ArrayList<String>() is really just a new ArrayList() at runtime, if a class extends ArrayList<String>, then the JVM knows that String is the actual type argument for List's type parameter.

In your case, you are making an anonymous subclass of the parameterized type, so the type information is retained. See the article for an in-depth explanation.

danben
Short remark: if your subclass simply passes the generic type (as in `public class SuperList<T> extends ArrayList<T>`), the output of the example code (without any anonymous classes involved) would be "Hi, my type parameter is T"
sfussenegger
A: 

Google Guice uses this to create TypeLiterals to represent generic classes at runtime. For example

TypeLiteral<List<String>> list = new TypeLiteral<List<String>>() {};

can be used, but

Class<List<String>> list = List<String>.class;

won't compile.

The technique is known as a 'super type token' (see Neal Gafter's article on the subject).

Ben Lings