Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing class with generic arguments to annotation

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?

like image 452
Mifeet Avatar asked Dec 23 '22 10:12

Mifeet


2 Answers

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 type C<T1,...,Tn>, where Ti (1 ≤ in) is a type, are all of the following:

  1. D<U1 θ,...,Uk θ>, where D<U1,...,Uk> is a generic type which is a direct supertype of the generic type C<T1,...,Tn> and θ is the substitution [F1:=T1,...,Fn:=Tn].

  2. C<S1,...,Sn>, where Si contains Ti (1 ≤ in).

  3. The type Object, if C<F1,...,Fn> is a generic interface type with no direct superinterfaces.

  4. 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 argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping):

  • ? extends T <= ? extends S if T <: 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:

  1. Class<? extends List<?>> is a supertype of Class<ArrayList> if ? extends List<?> contains ArrayList.

  2. ? 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 type C are all of the following:

  1. The direct superclass of the raw type C.

  2. The direct superinterfaces of the raw type C.

  3. The type Object, if C<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.

like image 86
Radiodef Avatar answered Jan 07 '23 14:01

Radiodef


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.

like image 23
Marv Avatar answered Jan 07 '23 16:01

Marv