Search code examples
javagenericsgeneric-collectionsunbounded-wildcard

What is the correct way to obtain the class object for class List without losing type information?


As a preface to my question, please note that I know what type erasure is. I feel the need to state this so useful answers don't get buried in basic explanations.

I would like to obtain the class object for class List. For a non-generic type, I would use a class literal like String.class, but for List, even though I can obtain the correct behavior, I still get warnings that I am doing it wrong.

From my understanding, the class object for List would be of type Class<List>, but even writing down that type,

Class<List> c = null;

gives a warning: "Raw use of parameterized class 'List'". Due to type erasure, the class object cannot have knowledge about list elements, so my next guess would be

Class<List<?>> c = null;

which as a type gets accepted. Next I have to obtain the class object, but every way of trying to write down a class literal fails:

Class<List<?>> l = List.class;

gives a hard type error, while

Class<List<?>> l = List<?>.class;

says "Cannot select from parameterized type".

Class<List<?>> l = (Class<List>)List.class;

Fails with the same hard type error as without the cast (expected since the cast is redundant), and

Class<List<?>> l = (Class<List<?>>)List.class;

says "inconvertible types".

Next, I tried the following invalid code snippet to learn something from the compiler error:

List<?> l = new ArrayList<>();
int x = l.getClass();

Now the compiler gives a type error for Class<? extends java.util.List> -- ignoring the "extends" part for a moment, which is obvious since getClass() could return a subclass, this tells me that .getClass() only wants to talk about the raw List type, not List<?> or similar. In a way, that seems correct to me, since the class object represents any List instance, not "lists whose type is unknown". However, I'm back to square one now because just writing down the raw type gives a warning, so I'm obviously not expected to do that (I understand that raw types can be used to deal with legacy code, but I'm not doing that here).

I understand that I could throw away all static type information about the class object like this:

Class<?> c = List.class;

But this is often not feasible, and indeed I'm trying to solve a larger problem where I need that type information.

While I know that I can easily disable warnings or use similar dirty tricks, I would like to know what the correct way is to obtain the class object for List.


Solution

  • There is no way you can win this war.

    There is no such thing as an instance of Class<List<?>>, or whatever. That's a valid type, but there is no concrete value that you can assign to that (other than null), because List.class and new ArrayList<>().getClass() have type Class<List> and Class<? extends List> respectively.

    Class instances are inherently raw-typed, because of type erasure.

    You can do unchecked casts:

    Class<List<?>> clazz = (Class<List<?>>) (Class<?>) List.class;
    

    but that will also generate warnings; and since you can do other unsafe casts, like:

    Class<List<?>> clazz1 = (Class<List<?>>) (Class<?>) ArrayList.class;
    

    you might end up in an odd situation where two instances of Class<List<?>> are unequal, or for example that sometimes a Class<List<?>> can be instantiated (using clazz.newInstance()), and other times not.


    Now, if you need some sort of generic type token, you can use something like what Guice does:

    TypeLiteral<ArrayList<?>> typeLiteral =
        new TypeLiteral<ArrayList<?>>() {};
    

    It is possible to obtain the ArrayList<?> from that at runtime, because of the way the superclass is captured (note the {} - this is an anonymous subclass of TypeLiteral). You can implement this yourself quite easily if you don't want to take a dependency on Guice (or other libraries which offer similar constructs), using typeLiteral.getClass().getGenericSuperclass().

    But whether that is an approach you can take depends on your undisclosed problem. The key takeaway is that generics and reflection just don't play nicely together.