9

Since generics were introduced, Class is parametrized, so that List.class produces Class<List>. This is clear.

What I am not able to figure out is how to get a instance of Class of type which is parametrized itself, i.e. Class<List<String>>. Like in this snippet:

public class GenTest {
    static <T> T instantiate(Class<T> clazz) throws Exception {
        return clazz.newInstance();
    }
    public static void main(String[] args) throws Exception {
        // Is there a way to avoid waring on the line below
        // without using  @SuppressWarnings("unchecked")?
        // ArrayList.class is Class<ArrayList>, but I would like to
        // pass in Class<ArrayList<String>>
        ArrayList<String> l = GenTest.instantiate(ArrayList.class);
    }
}

I run into variations of this problem quite often and I still don't know, if I just miss something, or if there is really no better way. Thanks for suggestions.

Michal
  • 1,262
  • 1
  • 12
  • 22

4 Answers4

8

The Class class is a run-time representation of a type. Since parametrized types undergo type erasure at runtime, the class object for Class would be the same as for Class<List<Integer>> and Class<List<String>>.

The reason you cannot instantiate them using the .class notation is that this is a special syntax used for class literals. The Java Language Specification specifically forbids this syntax when the type is parametrized, which is why List<String>.class is not allowed.

Avi
  • 19,934
  • 4
  • 57
  • 70
  • All the logic behind erasure is clear to me. The question is purely about how to associate T with ArrayList to make the method result type match the variable it is assigned to - to make the compiler happy without any warnings. – Michal Sep 16 '08 at 12:58
  • Any code working with Class objects will only have access to the types after erasure. So if you have a List intList, you will be able to call instantiate(intList.getClass()) and the actual Class object will be List.class. – Avi Sep 17 '08 at 08:27
  • @Avi Unfortunately, your example doesn't work: List stringList = Collections.emptyList(); instantiate(stringList.getClass()); gives The method instantiate(Class>) in the type ... is not applicable for the arguments (Class) – thSoft Nov 13 '12 at 16:56
1

Classes represent classes loaded by a class loader, which are raw types. To represent a parameterized type, use java.lang.reflect.ParameterizedType.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Can you expand on this, perhaps with an example to help out those who have never used the java.lang.reflect.ParameterizedType before? – Henry B Sep 16 '08 at 12:46
  • Anyway, the ParameterizedType interface does not provide any way to creare create a instance... – Nicolas Sep 16 '08 at 12:58
1

I don't think that you can do what you are trying. Firstly, your instantiate method doesn't know that its dealing with a parameterised type (you could just as easily pass it java.util.Date.class). Secondly, because of erasure, doing anything particularly specific with parameterised types at runtime is difficult or impossible.

If you were to approach the problem in a different way, there are other little tricks that you can do, like type inference:

public class GenTest
{
    private static <E> List<E> createList()
    {
        return new ArrayList<E>();
    }

    public static void main(String[] args)
    {
        List<String> list = createList();
        List<Integer> list2 = createList();
    }
}
Dan Dyer
  • 53,737
  • 19
  • 129
  • 165
0

The only thing you can do is instantiate List<String> directly and call its getClass():

instantiate(new List<String>() { ... }.getClass());

For types with multiple abstract methods like List, this is quite awkward. But unfortunately, calling subclass constructors (like new ArrayList<String>) or factory methods (Collections.<String>emptyList()) don't work.

thSoft
  • 21,755
  • 5
  • 88
  • 103