Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 601
CptWasp Avatar asked Feb 19 '15 10:02

CptWasp


2 Answers

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

like image 102
Jatin Avatar answered Oct 14 '22 07:10

Jatin


I think that in order to understand this, one must first understand the subtle differences between types and classes: objects have classes, references have types.

Classes vs Types

I think an example is a good way to explain what I mean here. Let assume the existence of the following class hierarchy:

class Mammal {}
class Feline extends Mammal {}
class Lion extends Feline {}
class Tiger extends Feline {}

Thanks to subtype polymorphism we could declare multiple references to the same object.

Tiger tiger = new Tiger(); //!
Feline feline = tiger;
Mammal mammal = feline;

Interestingly, if we asked everyone of these references the name of their class they all would reply with the same answer: "Tiger".

System.out.println(tiger.getClass().getName()); //yields Tiger
System.out.println(feline.getClass().getName()); //yields Tiger
System.out.println(mammal.getClass().getName()); //yield Tiger

This means the actual class of the object is fixed, its class is the one we used to instantiate it when we used the "new" operator in our code above.

The references, on the other hand, may have different types, polymorphically compatible with the actual class of object (i.e. a tiger in this case).

So, objects have a fixed class whereas references have compatible types.

This tends to be confusing, since the class names are the same thing we use to name the types of our references, but semantically speaking there is a subtle difference, as we can see above.

Perhaps the most confusing part is the realization that classes are also objects, and therefore they can have compatible references of their own. For instance:

Class<Tiger> tigerClass = null;
Class<? extends Tiger> someTiger = tigerClass;
Class<? extends Feline> someFeline = tigerClass;
Class<? extends Mammal> someMammal = tigerClass;

In my code above the object being referenced is a class object (which I left null for time being), and these references being used here have different types to reach that hypothetical object.

So, you see, the word "class" here is use to name a "type of reference" pointing to an actual class object whose type is compatible with any of these references.

In my example above I failed to define such class object since I initialized the original variable to null, though. This is intentional, and in a moment we'll see why.

About Invoking getClass and getSubclass on References

Following @Jatin's example that considers the getClass method, now, let's consider the following piece of polymorphic code:

Mammal animal = new Tiger();

Now we know that regardless of the fact that our animal reference is of type Mammal, the actual class of the object is, and always will be, Tiger (i.e. Class).

What should we get if we do this?

? type = mammal.getClass()

Should type be a Class<Mammal> or a Class<Tiger>?

Well, the problem is that when the compiler sees a reference of type Mammal, and it cannot tell what is the actual class of the object this reference is pointing to. That could only be determined at runtime, right?. It may in fact be a Mammal, but it may just as well be any of its subclasses, like a Tiger, right?

So, when we ask for its class, we don't get Class<Mammal> because the compiler cannot be sure. Instead we get a Class<? extends Mammal>, and this makes much more sense, because, after all, the compiler knows that based on the rules of subtype polymorphism the given reference may be pointing to a Mammal or any of its subtypes.

At this point, you can probably see the subtle nuances of using the word class here. It would seem that what we are actually getting out of the getClass() method is some kind of type reference that we use to point to the actual class of the original object as we had already explained before.

Well, the same thing could be said about the asSubclass method. For instance:

Mammal tiger = new Tiger();

Class<? extends Mammal> tigerAsMammal = tiger.getClass();
Class<? extends Feline> tigerAsFeline = tigerAsMammal.asSubclass(Feline.class);

When we invoke asSubclass we are getting a reference to the actual type of class being pointed to by our reference, but the compiler can no longer be sure of what that actual nature should be, and therefore you get a laxer reference like Class<? extends Feline>. This is the most the compiler can assume about the original nature of the object, and that's why this is all we get.

What about new Tiger().gerClass()?

We might expect that the only way to obtain a Class<Tiger> (without wirldcards) should be by accessing the original object, right? Like:

Class<Tiger> tigerClass = new Tiger().getClass()

Funny thing, though, we alway reach the tiger object through a reference type, right?. In Java we never have direct access to the objects. Since we are always reaching an object through their references, there is no way the compiler can make assumptions about the actual type of the reference being returned.

That's why even this code would produce Class<? extend Tiger>

Class<? extends Tiger> tigerClass = new Tiger().getClass();

That is, the compiler makes no guarantees about what the new operator may return here. For all that matters it might return an object compatible with Tiger, but not necessary one whose class is Tiger itself.

This becomes clearer if you change the new operator for a factory method.

TigerFactory factory = new TigerFactory();
Class<? extends Tiger> tigerClass = tigerFactory.newTiger().getClass();

And our Tiger factory:

class TigerFactory {
   public Tiger newTiger(){
     return new Tiger(){ } //notice this is an anonymous class
   }
}

I hope this somehow contributes to the discussion.

like image 45
Edwin Dalorzo Avatar answered Oct 14 '22 09:10

Edwin Dalorzo