I ran into trouble with Java's generics (who hasn't?). I couldn't figure out why this wouldn't compile:
class Foo {
public static void main(String[] args) {
List<Class<Foo>> list = null;
list.add(new Foo().getClass());
}
}
Here's the error (remember, it's a compile-time error):
The method add(Class<Foo>) in the type List<Class<Foo>> is not applicable for the arguments (Class<capture#2-of ? extends Foo>)
This is what I figured out:
First, as far as I know, that "capture#2-of" is just an internal name that the compiler assigns to the generic type and doesn't really have any great meaning, so basically the compiler seems to be complaining that Class<? extends Foo> isn't assignable to Class<Foo>, which is true, of course:
Class<? extends Foo> c1 = null;
Class<Foo> c2 = null;
c2 = c1; // <-- error
Same thing as:
List<? extends Object> l1 = null;
List<Object> l2 = null;
l2 = l1; // <-- error
This is illegal because consider if l1 is actually a List<String>, then:
l2.add(new Date());
would be a legal method call, and you've just added a Date to a List<String>.
So we know that Class<? extends Foo> isn't assignable to Class<Foo>, but the question remains: why does the compiler think we're trying to do that? Well, for the next step, what does java.lang.Object.getClass() return? Per the
API docs, it's a Class<?>, but that's not the whole story. We can see from our example that it's actually "Class<capture#2-of ? extends Foo>".
Now what I couldn't figure out was why the type of Class was "? extends Foo". Shouldn't getClass() called on a Foo object return something of type "Class<Foo>"? But I was forgetting about polymorphism and how it plays with these
compile-time parameterized types. In this code:
Foo foo = ...;
Class<...> c = foo.getClass();
the compile-time type of c can't be Class<Foo> because Foo is only the
declared type of the variable. At runtime, the foo variable could actually hold an instance of any subclass of Foo, meaning the type returned by getClass() must be at least as wide as Class<? Extends Foo>, which explains this last bit of the puzzle.
This is why the result of new Foo().getClass() can't be guaranteed to fit in a variable of type Class<Foo>.
For more good stuff on generics, Sun's
generics tutorial is very easy to understand.