Search code examples
javagenericseffective-java

Class.asSubclass signature


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.


Solution

  • 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.classand not AbstractList.class. Neither can ls.getClass return Class<ArrayList> because lsis 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