Monday, 30 July 2012

Java Generics II - Retrieving Type at Runtime

How to Retrieve the Type of a Generic at Runtime?

This is a very common question repeated over and over in forums and blogs. The answer is simple: it is impossible unless you use a workaround (or some kind reflexion, when possible, as we will see next).

In other words, there is no way you can achieve something like this:
public class MyQueue<T> extends PriorityQueue<T> {

    public boolean canTakeSuchElement(Object o) {
        return T.getClass().isInstance(o);
    }

}
Many have tried, nearly all failed (*). One workaround is the following:
public class MyQueue<T> extends PriorityQueue<T> {

    private Class<T> myElementType;

    public MyQueue(Class<T> elementType) {
        myElementType = elementType;
    }

    public boolean canTakeSuchElement(Object o) {
        return myElementType.isInstance(o);
    }

}

Which you can use as following:
MyQueue<String> mq = new MyQueue<String>(String.class);

if ( mq.canTakeSuchElement(333) ) {
    // No way...
} else if ( mq.canTakeSuchElement("azjsjdl") ) {
    // Ok...
}

Does Java Keep Any Trace of the Generic Instantiation Type at Runtime?

Contrary to the information commonly spread over the Internet, Java does keep some information at runtime about the types used to 'instantiate' generics. But it is not easy to retrieve it. See (*) and the next question.

How to Retrieve the Generic Type of a List, Set, Map... at Runtime?

It is possible using reflexion:
public class ExtractGenerics {

    public static class MyClass {

        List<String> l = new ArrayList<String>();
        Set<Integer> s = new HashSet<Integer>();
        Map<Double,String> m = new HashMap<Double,String>();

    }

    public static Class<?>[] extractGenericTypes(Field f) {

        ParameterizedType stringListType =
            (ParameterizedType) f.getGenericType();

        Type[] ata = stringListType.getActualTypeArguments();

        Class<?>[] result = new Class[ata.length];

        for (int i=0;i<ata.length;i++) {
            result[i] = (Class<?>) ata[i];
        }

        return result;

    }

    public static void printGenericTypes(Class<?>[] cs) {

        System.out.println("------");
        for ( Class c : cs ) {
            System.out.println(c);
        }
    }

    public static void main(String... args) throws Exception {

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("l")));

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("s")));

        printGenericTypes(
          extractGenericTypes(
            MyClass.class.getDeclaredField("m")));

    }

}

The above produces:

  ------
  class java.lang.String
  ------
  class java.lang.Integer
  ------
  class java.lang.Double
  class java.lang.String


(*) The concept of super token type has been introduced by Neil Gafter. It can be exploited via some complicated tricks to extract the Type (not a Class instance) used to 'instantiate' the generic type. Ian Robertson provides technical explanation here.

Although we won't develop it here, super token types can prove useful to create instances of the type used to 'instantiate' the generic type, when possible. If, with MyClass<T>, you create MyClass<List<String>>, you cannot instantiate a List<String>. However, if you create MyClass<ArrayList<String>>, you can instantiate ArrayList<String> (more on this in Neil Gafter's post mentioned earlier).

Yet, this solution does not cover well for constructors with parameters.


Part I  Part II   Part III   Part IV

No comments:

Post a Comment