my question is quite theoretic... This is the signature of Class.asSubclass (Javadoc):
public <U> Class<? extends U> asSubclass(Class<U> clazz)
Why are wildcard generics used in the return type? From my understanding of generics a better signature could be:
public <U> Class<U> asSubclass(Class<U> clazz)
because you can for sure cast
Class<? extends U>
to a more simple
Class<U>
Bloch in his book "Effective Java" recommends (page 137, item 28):
Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.
What is the reason behind this choice? What I'm missing? Thank you very much in advance.
Edit: As @egelev suggests, I could have indeed phrased my question in another manner... in fact returning the input parameter "as is" would be meaningless. So the real problem is: What is the real usefulness of the Class.asSubclass method, compared to a normal cast? Both would throw a ClassCastException in case of cast problems.
MAYBE it has been added to avoid unchecked casting in a specific situation: when you pass the result of the asSubclass method directly to another method asking for a constrained type parameter, as here (taken from Effective Java, page 146):
AnnotatedElement element;
...
element.getAnnotation(annotationType.asSubclass(Annotation.class));
The signature of the above method is:
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
It seems to me that the asSubclass method is only a way to do an (in fact!) unchecked cast without a proper compiler warning...
And this in the end re-propose my former question: the signature
public <U> Class<U> asSubclass(Class<U> clazz)
would be equally effective (even if strange, I admit it)! It would be fully compatible with the getAnnotation example, and would not constrain client code forcing it to use pointlessly wildcard generics.
Edit2: I think my general question has been solved; thank you very much. If someone has other good examples about the correctness of the asSubclass signature please add them to the discussion, I would like to see a complete example using asSubclass with my signature and evidently not working.
In case of the return type Class<? extends U>
. Lets first try understanding, the getClass
signature:
AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();
Now had the compiler allowed us to do:
Class<AbstractList> x = ls.getClass();
This would have been wrong. Because at runtime, ls.getClass
would be ArrayList.class
and not AbstractList.class
. Neither can ls.getClass
return Class<ArrayList>
because ls
is of type AbstractList<> and not Arraylist
So the compiler, now says - Ok! I cant return Class<ArrayList>
nor can I return Class<AbstractList>
. But because I know ls
is an AbstractList for sure, so the actual class object can only be a subtype of AbstractList. So Class<? extends AbstractList>
is a very safe bet. Because of wild card:
You cannot do:
AbstractList<String> ls = new ArrayList<>();
Class<? extends AbstractList> x = ls.getClass();
Class<ArrayList<?>> xx = x;
Class<AbstractList<?>> xxx = x;
The same logic applies to your question. say suppose it was declared as:
public <U> Class<U> asSubClass(Class<U> c)
The below would have compiled:
List<String> ls = new ArrayList<>();
Class<? extends List> x = ls.getClass();
Class<AbstractList> aClass = x.asSubclass(AbstractList.class); //BIG ISSUE
Above aClass
at runtime is Class<Arraylist>
and not Class<AbstractList>
. So this should not be allowed!! Class<? extends AbstractList>
is the best bet.
The first thought I had on looking at the question was, why wasn't it declared as:
public <U extends T> Class<? extends U> asSubClass(Class<U> c)
it makes more sense to have a compile time restriction on the arguments I can pass. But the reason I think it was not preferred was - this would have broken the backward compatibility of pre-java5 code. For example, code such as below which compiled with pre-Java5 would no longer compile if it asSubClass
was declared as shown above.
Class x = List.class.asSubclass(String.class); //pre java5
// this would have not compiled if asSubclass was declared above like
Quick check:
public static <T, U extends T> Class<? extends U> asSubClaz(Class<T> t, Class<U> c){
return t.asSubClass(c);
}
public static <T, U> Class<? extends U> asSubClazOriginal(Class<T> t, Class<U> c){
return t.asSubClass(c);
}
asSubClazOriginal(List.class, String.class);
asSubClaz(List.class, String.class); //error. So would have broken legacy code
PS: For the edited question, on why asSubClass
rather than cast? - Because cast is betrayal. For example:
List<String> ls = new ArrayList<>();
Class<? extends List> x = ls.getClass();
Class<AbstractList> aClass = (Class<AbstractList>) x;
Above would always succeed because generics are erased. So its class cast to a class. But aClass.equals(ArrayList.class)
would give false. So definitely cast is wrong. In case you need type safety, you can use asSubClaz
above