Get type of a generic parameter in Java with reflection

Viewed 220394

Is it possible to get the type of a generic parameter?

An example:

public final class Voodoo {
    public static void chill(List<?> aListWithTypeSpiderMan) {
        // Here I'd like to get the Class-Object 'SpiderMan'
        Class typeOfTheList = ???;
    }

    public static void main(String... args) {
        chill(new ArrayList<SpiderMan>());
    }
}
18 Answers

One construct, I once stumbled upon looked like

Class<T> persistentClass = (Class<T>)
   ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];

So there seems to be some reflection-magic around that I unfortunetly don't fully understand... Sorry.

Nope, that is not possible. Due to downwards compatibility issues, Java's generics are based on type erasure, i.a. at runtime, all you have is a non-generic List object. There is some information about type parameters at runtime, but it resides in class definitions (i.e. you can ask "what generic type does this field's definition use?"), not in object instances.

Because of type erasure the only way to know the type of the list would be to pass in the type as a parameter to the method:

public class Main {

    public static void main(String[] args) {
        doStuff(new LinkedList<String>(), String.class);

    }

    public static <E> void doStuff(List<E> list, Class<E> clazz) {

    }

}

No it isn't possible.

You can get a generic type of a field given a class is the only exception to that rule and even that's a bit of a hack.

See Knowing type of generic in Java for an example of that.

I noticed that many people lean towards the getGenericSuperclass() solution:

class RootGeneric<T> {
  public Class<T> persistentClass = (Class<T>)
    ((ParameterizedType)getClass().getGenericSuperclass())
      .getActualTypeArguments()[0];
}

However, this solution is error prone. It will not work properly if there are generics in the descendants. Consider this:

class Foo<S> extends RootGeneric<Integer> {}

class Bar extends Foo<Double> {}

Which type will Bar.persistentClass have? Class<Integer>? Nope, it will be Class<Double>. This will happen due to getClass() always returns the top most class, which is Bar in this case, and its generic super class is Foo<Double>. Hence, the argument type will be Double.

If you need a reliable solution which doesn't fail I can suggest two.

  1. Use Guava. It has a class that was made exactly for this purpose: com.google.common.reflect.TypeToken. It handles all the corner cases just fine and offers some more nice functionality. The downside is an extra dependency. Given you've used this class, your code would look simple and clear, like this:
class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  public final Class<T> persistentClass = (Class<T>) (new TypeToken<T>(getClass()) {}.getType());
}
  1. Use the custom method below. It implements a significantly simplified logic similar to the Guava class, mentioned above. However, I'd not guarantee it's error prone. It does solve the problem with the generic descendants though.
abstract class RootGeneric<T> {
  @SuppressWarnings("unchecked")
  private Class<T> getTypeOfT() {
    Class<T> type = null;
    Class<?> iter = getClass();
    while (iter.getSuperclass() != null) {
      Class<?> next = iter.getSuperclass();
      if (next != null && next.isAssignableFrom(RootGeneric.class)) {
        type =
            (Class<T>)
                ((ParameterizedType) iter.getGenericSuperclass()).getActualTypeArguments()[0];
        break;
      }
      iter = next;
    }
    if (type == null) {
      throw new ClassCastException("Cannot determine type of T");
    }
    return type;
  }
}

Here is another trick. Use a generic vararg array

import java.util.ArrayList;

class TypedArrayList<E> extends ArrayList<E>
{
    @SafeVarargs
    public TypedArrayList (E... typeInfo)
    {
        // Get generic type at runtime ...
        System.out.println (typeInfo.getClass().getComponentType().getTypeName());
    }
}

public class GenericTest
{
    public static void main (String[] args)
    {
        // No need to supply the dummy argument
        ArrayList<Integer> ar1 = new TypedArrayList<> ();
        ArrayList<String> ar2 = new TypedArrayList<> ();
        ArrayList<?> ar3 = new TypedArrayList<> ();
    }
}

This is impossible because generics in Java are only considered at compile time. Thus, the Java generics are just some kind of pre-processor. However you can get the actual class of the members of the list.

Related