Reputation: 102
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
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
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