ntson9p
ntson9p

Reputation: 102

Java Generics: Incompatible types: capture<? extends Object> and <? extends T>

I have an object of type T (generic)

public <T> void method1(T object) {...}

I want to use the method getClass() on that object.

Class<? extends T> clazz = object.getClass();

but the compiler shows error:

Incompatible types.
Found:    'java.lang.Class<capture<? extends java.lang.Object>>'
Required: 'java.lang.Class<? extends T>'

How can I correct my code so that the compiler will compile, and the clazz object will have generic type T, or <? extends T> without casting ?

Upvotes: 1

Views: 5019

Answers (2)

newacct
newacct

Reputation: 122439

To add to the other answer, the reason that .getClass() returns Class<? extends |X|>, where |X| is the erasure of the static type of the expression on which getClass is called, is that X might be a parameterized type, and it is unsafe to have a Class<a parameterized type> or a Class<? extends a parameterized type>.

For example, consider Class<ArrayList<String>>. But there's actually only a single Class instance representing ArrayList -- it is not specific for ArrayList<String> or ArrayList<Integer>, etc. If you had an expression of type Class<ArrayList<String>>, you could call .cast() on it and pass any object and get an ArrayList<String> without warnings, but that would be deceptive, because it didn't really check that the object is an ArrayList<String> (there's no way to do that at runtime); it only checked that it was an ArrayList; you may still get a nasty surprise down the line if it's an ArrayList<Integer>. Similarly, with a Class<ArrayList<String>>, you could call .isInstance() on it and pass an ArrayList<Integer> and get true.

The same thing is true for a Class<? extends ArrayList<String>>; i.e. you could call .cast() on it and get an ArrayList<String>, and you could call .isInstance() on it, and get deceptive results. Those are unsafe. So you should not be able to get type Class<? extends ArrayList<String>> without a warning. Since in your code it's possible for T to be a parameterized type (the compiler must assume that a type variable can be any type that is within its bounds), the compiler can't give you a Class<? extends T> without a warning. It must return a type known at compile time to be safe, and the option chosen by the language designers is Class<? extends |T|>, which in your case is Class<? extends Object>.

Upvotes: 1

rzwitserloot
rzwitserloot

Reputation: 102892

Ugly-cast is the only way:

Class<? extends T> clazz = (Class<? extends T>) object.getClass();

... and then suppress the warning that you get when you do this using @SuppressWarnings.

More generally, it's almost always a mistake to even want the Class object. Perhaps expand on why you think you need it, there is probably a nicer alternative.

For actual types, the code works fine (see documentation), but not for typevars:

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:

The 'erasure of the static type' is just Object for your specific T here, hence, you get Class<? extends Object>, which is just Class<?>, which you can't assign to a variable of type Class<? extends T> unless you ugly-cast. Why does it work this way? Eh, it does, spec says so. I admit I don't know why, but it is what it is.

NB: You could also add a parameter: void foo(Class<? extends T> clazz, T value) but now you're bothering callers with passing a seemingly useless additional parameter. Probably not a good idea. The compiler will at least check your callers and complain if they pass e.g. String.class and then pass an integer as a value.

Upvotes: 4

Related Questions