18

I feel utterly silly for having to ask this, but I'm failing to understand why doesn't the following Java code compile:

void <T> doSomething(List<T> items) {
    Class<? extends T> clazz = items.get(0).getClass();
    ...
}

From Java doc:

The actual result type is Class< ? extends |X|> where |X| is the erasure of the static type of the expression on which getClass is called. For example, no cast is required in this code fragment:

Number n = 0; Class< ? extends Number> c = n.getClass();

EDIT:

  1. Found this nice explanation of what erasure of the static type means.

  2. There's a way to preserve generic type information using subclasses, known as super type token. A now deleted answer helpfully pointed out that Guava library has a convenient utility for exploiting this.

  3. Here's a great article on extracting generic types reflectively and a nice lib, called GenTyRef, simplifying it

  4. I forked GenTyRef to add support for working with AnnotatedTypes (introduced in Java 8). It's called GeantyRef (and it's in Maven Central)

kaqqao
  • 12,984
  • 10
  • 64
  • 118

2 Answers2

25

The erasure of the static type of items.get(0) is Object (since T is erased during compilation).

Therefore items.get(0).getClass() returns a Class<? extends Object>, not a Class<? extends T>, which explains why your attempted assignment fails.

This will pass compilation :

Class<? extends Object> clazz = items.get(0).getClass();

If you want the Class of the generic parameter to be known by that method, you can pass it as an additional argument.

void doSomething(List<T> items, Class<T> clazz) {

}
Eran
  • 387,369
  • 54
  • 702
  • 768
  • 2
    `Class extends Object>` is redundant, `Class>` is more concise – user140547 May 23 '16 at 08:54
  • @user140547 I was following the language of the Javadoc (`Class extends |X|>`). – Eran May 23 '16 at 08:55
  • 1
    @Eran `Class extends T> clazz` isn't safe - you could call `doSomething(Arrays.asList(Double.valueOf(0)), Integer.class)`. You'd need to use ` super T>`, no? – Andy Turner May 23 '16 at 08:58
  • Hmm, this doesn’t explain *why* the result bears the “erasure of the static type of the expression” instead of just the “static type of the expression”… – Holger May 23 '16 at 17:18
  • @Holger: It's because `T` can represent non-reifiable types like `ArrayList`. But class objects only represent reifiable types. There is only a `Class`, not a `Class>`. – newacct May 26 '16 at 00:31
  • @newacct: what kind of danger would implicitly representing `ArrayList.class` as a `Class>` create? Keep in mind that we’re talking about code having only a `T [extends Object]` and a `Class`, not even knowing that `T` is a `List` from the caller’s perspective… – Holger May 26 '16 at 08:06
  • @Holger: Well, for example you can use it to `.cast()` anything into `ArrayList` without a unchecked warning (even though the type argument is not checked). Also, some things that ought to compile don't compile, like `List a; List b; if (a.getClass() == b.getClass()) ...` – newacct May 26 '16 at 18:57
  • 1
    @newacct: Now *that* should perhaps be included in the answer… – Holger May 26 '16 at 19:06
1

First of all, you need to define T as type parameter for the method: <T> void doSomething. Then you need a cast to Class<? extends T>. So this is how you can put together:

<T> void doSomething(List<T> items) {
    Class<? extends T> clazz = (Class<? extends T>) items.get(0).getClass();
    ...
}
Tamas Rev
  • 7,008
  • 5
  • 32
  • 49
  • 1
    It is strange how erasure forces us to cast again. –  May 23 '16 at 08:58
  • 1
    The missing was a typo. Casting will obviously work, but I needed an explanation as to why it was needed. – kaqqao May 23 '16 at 08:59