简体   繁体   中英

Is it possible to implement method with signature List<Class<? extends Annotation>> in Java?

The problem is in a generic restriction:

public List<Class<? extends Annotation>> getAnnotations() {
    return new ArrayList<>(Arrays.asList(Override.class));
}

Real return type is ArrayList<Class<Override>>
Method expects List<Class<? extends Annotation>> List<Class<? extends Annotation>>

Class<Override> is a subtype of Class<? extends Annotation> Class<? extends Annotation>
Class<? extends Annotation> c = Override.class; //allowed

ArrayList is a subtype of a List , if types of the elements match:
List<? extends Number> l = new ArrayList<Integer>(); // allowed

However, this is not allowed:

List<Class<? extends Annotation>> l = Arrays.asList(Override.class);
List<Class<? extends Annotation>> l = new ArrayList<>(Arrays.asList(Override.class));

Is it even possible or Class wildcards are broken?

I would assume that this is because of the jdk 1.7 type inference nature.

As you may already know, the Arrays.asList(T ... elems) method is generic, but we seldom explicitly specify the type-parameter which we'd like the method to work with and thus we rely on the type inference feature of the compiler.

So, when the compiler sees an Arrays.asList(Override.class) statement it will infer that the type-parameter for the method should be replaced with Class<Override> , ie we'd have a version of the method in this form:

public List<Class<Override>> asList(Class<Override> ... elems)

However, if you explicitly set the type parameter for the method to

List<Class<? extends Annotation>> l = 
               Arrays.<Class<? extends Annotation>>asList(Override.class);

then the compiler will actually know what the type-parameter has to be replaced with and then the version of the .asList() method would be:

public List<? extends Annotation> asList(Class<? extends Annotation> ... elems)

Now this will compile fine, since Class<? extends Annotation> Class<? extends Annotation> is compatible to Class<Override> . In Java8, the type inference feature is improved even more, so that you don't have to explicitly set the type-parameter for the .asList() method.

However, the more interesting question goes to

Why List<Class<Override>> is not compatible with List<Class<? extends Annotation>> List<Class<? extends Annotation>> ?

The java.lang.Class is a final one, which would help answering the following two questions, the combination of which will answer the above question. :)

So,

  • What does a List<Class<Override>> mean?

List<Class<Override>> means that we can add only instances of Class<Override> and nothing else to the list. Which is great, knowing that we can't even add Class<Override> sub-classes, since the Class type is final .

  • What does a List<Class<? extends Annotation>> List<Class<? extends Annotation>> mean?

This type of List represents a whole family of lists of classes, all of which are subclasses of the Annotation type, which means that we can successfully add any annotation type (for example, SuppressWarnings.class , Override.class , Documented.class , etc.) to the list.

Lets assume that the following example was actually correct:

List<Class<Override>> overrides = Arrays.asList(Override.class);
List<Class<? extends Annotation>> annotations = new ArrayList<>();
annotations = overrides;
annotations.add(SuppressWarnings.class); //HUGE PROBLEM
annotations.add(Documented.class); //ANOTHER HUGE PROBLEM

The two huge problems come from the fact that we're trying to add some non- Override instances to the overrides , which is very wrong.

We have smart enough compiler that can actually detect such possible problems and throwing a compile-time error is the way to prevent us from doing this.

More info:

ArrayList is a subtype of a List, if types of the elements match:

 List<? extends Number> l = new ArrayList<Integer>(); // allowed 

Yes, but in your example the element types do not match:

List<Class<? extends Annotation>> l = new ArrayList<Class<Override>>();

Granted, Class<Override> is a subtype of Class<? extends Annotation> Class<? extends Annotation> , but just like List<String> is not a subtype of List<Object> , List<Class<Override>> is not a subtype of List<Class<? extends Annotation>> List<Class<? extends Annotation>> . It would be a subtype of List<? extends Class<? extends Annotation>> List<? extends Class<? extends Annotation>> List<? extends Class<? extends Annotation>> , though.

That said, the reason your code does not compile is that in Java 7, type inference does not take into account the method's return type when inferring the type of a return statement's expression, so it defaults to the most specific type that could be assigned to

Arrays.asList(Override.class)

not realizing that the return statement would only compile with a more flexible type (Java 8 type inference is smarter, btw). One workaround is to explicity specify the type argument:

Arrays.<Class<? extends Annotation>(Override.class);

or give Java 7's type inference a hint by assigning to a local variable first:

List<Class<? extends Annotation>> list = Arrays.asList(Override.class);
return list;

or change the method return type to

List<? extends Class<? extends Annotation>> getAnnotations()

so the inferred type does not matter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM