When generics were first introduced, getClass
returned Class<? extends X>
, where X
was the static type of the expression on which it was called. This behavior led to unreasonable compilation issues, as reported in this Oracle bug. Here is that bug report's example:
The following program fragment fails to compile
void f(List<Integer> li, List<String> ls) {
if (li.getClass() == ls.getClass())
;
}
because the intersection of Class<List<Integer>>
and
Class<List<String>>
is empty.
This issue was resolved by widening the return type of getClass
to be what it is now. From the documentation:
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.
This resolved the above issue but consequently led to the issue that your question points out. Not long after, another bug was reported, arguing the following:
I think the getClass()
typing rule could be changed to Class<? extends wildcard(T)>
The wildcard operation is defined by: if T
is parametrized,
wildcard(T)=erasure(T)<?>
else ,
wildcard(T)=T
JUSTIFICATION :
This rule introduce a raw type. Raw type must ONLY be used to interact with legacy code.
The new Rule introduce a wildcard. Relationship between parametrized type and wildcard are based on subtyping rules. Relationship between parametrized type and wildcard are based on raw type conversion.
This bug was not acted upon and remains open to this day, with the following counterarguments:
The proposal means that getClass()
would return a Class<? extends ArrayList<?>>
object, which is incompatible with other Class<? extends ArrayList<?>>
objects. This is compatible with existing code like:
List<String> l = ...;
Class<? extends List> c = l.getClass();
because the new type of the RHS, Class<? extends List<?>>
, is a subtype of
Class<? extends List>
.
A disadvantage of enriching Class's type argument is that it will
break idiomatic use of Class.cast
. Today, you can write:
List<Integer> x = ...;
Class<? extends List> cl = x.getClass();
List<Integer> y = cl.cast(null);
and get a warning at cast()
, because of the unchecked conversion from List
to List<Integer>
. But with the proposal, the analogous code doesn't compile:
List<Integer> x = ...;
Class<? extends List<?>> cl = x.getClass();
List<Integer> y = cl.cast(null);
because List<?>
returned by cast()
cannot be converted
to List<Integer>
. The only way to avoid the error is to cast
cl.cast(..)
up to List
and suffer the unchecked conversion warning to
List<Integer>
. This is effectively what getClass()
does already.
Overall, the proposal seems like a good idea, but it has moderate
complexity and a fairly small payoff.
(abridged and with some typos corrected)