<? extends Class> and <? super Class> in Java - why it works this way?

List<? super Person> list = new ArrayList<>();
list.add(clarissa); // clarissa is an instance of Student class

The reason why you can do things above, Suppose there is a Person class, Student class extends Person. You can think List means that in this List all elements is of class Person or superclass of Person, so when you add Person class instance or subclass of Person class, the implicit casting will happen. e.g. Person person = new Student();

public static void main(String[] args) {
        ArrayList<? super Person> people = new ArrayList<>();
        people.add(new Person());
        people.add(new Object()); // this will not compile
}

But if you add Object class instance into the list, explicit casting or down casting is required and the compiler does not know whether say (Person) Object can be successful.


The way I look at it is this - the placeholder T stands in for a definite type and in places where we need to know the actual type we need to be able to work it out. In contrast the wildcard ? means any type and I will never need to know what that type is. You can use the extends and super bounds to limit that wildcard in some way but there's no way to get the actual type.

So, if I have a List<? extends MySuper> then all I know about it is that every object in it implements the MySuper interface, and all the objects in that list are of the same type. I don't know what that type is, only that it's some subtype of MySuper. That means I can get objects out of that list so long as I only need to use the MySuper interface. What I can't do is to put objects into the list because I don't know what the type is - the compiler won't allow it because even if I happen to have an object of the right type, it can't be sure at compile time. So, the collection is, in a sense a read-only collection.

The logic works the other way when you have List<? super MySuper>. Here we're saying the collection is of a definite type which is a supertype of MySuper. This means that you can always add a MySuper object to it. What you can't do, because you don't know the actual type, is retrieve objects from it. So you've now got a kind of write-only collection.

Where you use a bounded wildcard versus the 'standard' generic type parameter is where the value of the differences start to become apparent. Let's say I have 3 classes Person, Student and Teacher, with Person being the base that Student and Teacher extend. In an API you may write a method that takes a collection of Person and does something to every item in the collection. That's fine, but you really only care that the collection is of some type that is compatible with the Person interface - it should work with List<Student> and List<Teacher> equally well. If you define the method like this

public void myMethod(List<Person> people) {
    for (Person p: people) {
        p.doThing();
    }
}

then it can't take List<Student> or List<Teacher>. So, instead, you would define it to take List<? extends Person>...

public void myMethod(List<? extends Person> people){
    for (Person p: people) {
        p.doThing();
    }
}

You can do that because myMethod never needs to add to the list. And now you find that List<Student> and List<Teacher> can both be passed into the method.

Now, let's say that you've got another method which wants to add Students to a list. If the method parameter takes a List<Student> then it can't take a List<People> even though that should be fine. So, you implement it as taking a List<? super Student> e.g.

public void listPopulatingMethod(List<? extends Student> source, List<? super Student> sink) {
    for (Student s: source) {
        sink.add(s);
    }
}

This is the heart of PECS, which you can read about in much greater detail elsewhere... What is PECS (Producer Extends Consumer Super)? http://www.javacodegeeks.com/2011/04/java-generics-quick-tutorial.html

Tags:

Java

Generics