DPM
DPM

Reputation: 1660

Java reflection get runtime type when using generics

I am wondering how can I get the runtime type which is written by the programmer when using generics. For example if I have class Main<T extends List<String>> and the programmer write something like

Main<ArrayList<String>> main = new Main<>();

how can I understand using reflection which class extending List<String> is used?

I'm just curious how can I achieve that. With

main.getClass().getTypeParameters()[0].getBounds[] 

I only can understand the bounding class (not the runtime class).

Upvotes: 3

Views: 1699

Answers (1)

Daniel Pryden
Daniel Pryden

Reputation: 60957

As the comments above point out, due to type erasure you can't do this. But in the comments, the follow up question was:

I know that the generics are removed after compilation, but I am wondering how then ClassCastException is thrown runtime ? Sorry, if this is a stupid question, but how it knows to throws this exception if there isn't any information about classes.

The answer is that, although the type parameter is erased from the type, it still remains in the bytecode.

Essentially, the compiler transforms this:

List<String> list = new ArrayList<>();
list.add("foo");
String value = list.get(0);

into this:

List list = new ArrayList();
list.add("foo");
String value = (String) list.get(0); // note the cast!

This means that the type String is no longer associated with the type ArrayList in the bytecode, but it still appears (in the form of a class cast instruction). If at runtime the type is different you'll get a ClassCastException.

This also explains why you can get away with things like this:

// The next line should raise a warning about raw types
// if compiled with Java 1.5 or newer
List rawList = new ArrayList();

// Since this is a raw List, it can hold any object.
// Let's stick a number in there.
rawList.add(new Integer(42));

// This is an unchecked conversion. Not always wrong, but always risky.
List<String> stringList = rawList;

// You'd think this would be an error. But it isn't!
Object value = stringList.get(0);

And indeed if you try it, you'll find that you can safely pull the 42 value back out as an Object and not have any errors at all. The reason for this is that the compiler doesn't insert the cast to String here -- it just inserts a cast to Object (since the left-hand side type is just Object) and the cast from Integer to Object succeeds, as it should.

Anyhow, this is just a bit of a long-winded way of explaining that type erasure doesn't erase all references to the given type, only the type parameter itself.

And in fact, as a now-deleted answer here mentioned, you can exploit this "vestigial" type information, through a technique called Gafter's Gadget, which you can access using the getActualTypeArguments() method on ParameterizedType.

The way the gadget works is by creating an empty subclass of a parameterized type, e.g. new TypeToken<String>() {}. Since the anonymous class here is a subclass of a concrete type (there is no type parameter T here, it's been replaced by a real type, String) methods on the type have to be able to return the real type (in this case String). And using reflection you can discover that type: in this case, getActualTypeParameters()[0] would return String.class.

Gafter's Gadget can be extended to arbitrarily complex parameterized types, and is actually often used by frameworks that do a lot of work with reflection and generics. For example, the Google Guice dependency injection framework has a type called TypeLiteral that serves exactly this purpose.

Upvotes: 5

Related Questions