Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>>

Class<Override> is a subtype of 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?

like image 493
AdamSkywalker Avatar asked Oct 19 '22 07:10

AdamSkywalker


1 Answers

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>, i.e. 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> 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>>?

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

  • What do multi-level wildcards mean?
  • Is List<Dog> a subclass of List<Animal>?
like image 179
Konstantin Yovkov Avatar answered Oct 27 '22 01:10

Konstantin Yovkov