I would like to know why this assignment is not accepted by Java compiler:
Class<? extends List<?>> blbost = ArrayList.class;
Please note that I'm not interested in solution such as Class<? extends List<?>> blobst = (Class<? extends List<?>>)ArrayList.class;
, I wonder what is the reason why it can be used.
The original motivation is using generics in an annotation:
@SomeAnnotation(SomeFunction.class) // Syntax error here!
private static class SomeClass {
}
private @interface SomeAnnotation {
Class<? extends Function<?, ?>> value();
}
private static class SomeFunction<T> implements Function<T, String> {
@Override
public String apply(T t) {
return t.toString();
}
}
I cannot use the cast in annotation because the value must be constant.
Why can't I pass SomeFunction
as an argument to SomeAnnotation
and what can I do about it?
First, the short answer is that Class<ArrayList>
is not a subtype of Class<? extends List<?>>
. There is actually a relationship like in the following diagram:
Class
|
Class<?>
|
Class<? extends List>
/ \
Class<? extends List<?>> Class<? extends ArrayList>
| |
Class<List<?>> Class<ArrayList>
In other words, Class<ArrayList>
and Class<? extends List<?>>
share a common supertype.
This is mostly covered in sections 4.10.2 and 4.5.1 of the specification.
An assignment such as Class<? extends List<?>> cls = ArrayList.class
would compile if Class<? extends List<?>>
was a supertype of Class<ArrayList>
, so we need to find out what the supertypes of Class<ArrayList>
are:
Given a generic type declaration
C<F1,...,Fn>
(n > 0), the direct supertypes of the parameterized typeC<T1,...,Tn>
, whereTi
(1 ≤ i ≤ n) is a type, are all of the following:
D<U1 θ,...,Uk θ>
, whereD<U1,...,Uk>
is a generic type which is a direct supertype of the generic typeC<T1,...,Tn>
andθ
is the substitution[F1:=T1,...,Fn:=Tn]
.
C<S1,...,Sn>
, whereSi
containsTi
(1 ≤ i ≤ n).The type
Object
, ifC<F1,...,Fn>
is a generic interface type with no direct superinterfaces.The raw type
C
.
Rule 1 is basically just saying that if you have a type e.g. ArrayList<String>
then it has supertypes List<String>
, Iterable<String>
, etc., where the type argument is merely substituted in to the superclass or superinterface. Class
has no relevant supertypes of this kind, because for this particular example we don't care about its superclasses or superinterfaces.
Rule 2 is the only one that we're actually interested in here, and the JLS directs us to 4.5.1 for an explanation of when a type argument contains another type argument:
A type argument
T1
is said to contain another type argumentT2
, writtenT2 <= T1
, if the set of types denoted byT2
is provably a subset of the set of types denoted byT1
under the reflexive and transitive closure of the following rules (where<:
denotes subtyping):
? extends T <= ? extends S
ifT <: S
[...]
T <= ? extends T
[...]
Using those two rules and the transitive property we can deduce a rule that a type argument ? extends S
contains a type argument T
if S
is a supertype of T
.
Using this rule, we can now frame the original problem as follows:
Class<? extends List<?>>
is a supertype of Class<ArrayList>
if ? extends List<?>
contains ArrayList
.
? extends List<?>
contains ArrayList
if List<?>
is a supertype of ArrayList
.
So what are the supertypes of the raw type ArrayList
? We can refer back to 4.10.2:
Given a generic type declaration
C<F1,...,Fn>
(n > 0), the direct supertypes of the raw typeC
are all of the following:
The direct superclass of the raw type
C
.The direct superinterfaces of the raw type
C
.The type
Object
, ifC<F1,...,Fn>
is a generic interface type with no direct superinterfaces.
(Rule 3 is irrelevant to the example we're thinking about here.)
Also, we need a quick jump to 4.8:
The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.
In other words, the supertypes of ArrayList
are all of the raw types AbstractList
, List
, Iterable
, etc.
From all of this, we can finally conclude that Class<ArrayList>
is not a subtype of Class<? extends List<?>>
, and this is why the assignment in the question doesn't compile.
As far as solutions that don't involve casting, Marv has shown one of them, which is to change Class<? extends Function<?, ?>>
to Class<? extends Function>
.
Another would be to never use e.g. a SomeFunction.class
, which has the raw type argument Class<SomeFunction>
. Instead, you'd always extend SomeFunction
with concrete type arguments. For example, if you want a SomeFunction<Double>
, you'd create an explicit subclass:
class DoubleFunction extends SomeFunction<Double> {}
And then you'd be able to use DoubleFunction.class
.
The reason why ArrayList
is not a subtype of List<?>
is because generics in Java are invariant
, meaning that for any two distinct types Type1
and Type2
, List<Type1>
is neither a sub- nor a supertype of List<Type2>
.
This also extends to raw types and parameterized types, meaning that a raw type like ArrayList
can never be sub- nor super type of a parameterized type like List<Type1>
and, perhaps more intuitively, a parameterized type like ArrayList<Type1>
can never be a subtype of a raw type like List
, even though ArrayList
is a subtype of List
.
Removing the wildcard from the inner type will allow you to pass the raw type class into the annotation:
import java.util.function.Function;
public class Example {
@SomeAnnotation(SomeFunction.class)
private static class SomeClass {
}
private @interface SomeAnnotation {
Class<? extends Function> value();
}
private static class SomeFunction<T> implements Function<T, String> {
@Override
public String apply(T t) {
return t.toString();
}
}
}
Not also that it is impossible to obtain a class literal from a parameterized type like List<String>
. For any type Type
, List<Type>.class
will be a syntax error, so using the raw type here is permissible, even though generally you shouldn't use raw types.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With