Java has call-site variance, which means the caller gets to decide which direction the subclass relationship goes in generic relationships.
Let's assume we've got a class Parent
and a subclass called Child
. Generally speaking, it's not safe to treat a List<Child>
as a List<Parent>
or vice versa. If we try to cast a List<Child>
to a List<Parent>
then someone could come along and .add
something that isn't a Child
to it, breaking our list. Conversely, if we try to cast a List<Parent>
to a List<Child>
and it contains some things that aren't Child
instances, we're equally out of luck.
However, each cast still provides us with a guarantee. If I have a List<Child>
, then I know that every element of that list is a Child
and hence (by the 'is-a' relationship established by subclasses) every element of that list is a Parent
. So if we only intend to read from the list and never to write to it, it's safe to take a List<Child>
and treat it as a read-only version of List<Parent>
. That's exactly what List<? extends Parent>
is. It says "I've got a list, it contains some subclass of Parent
and I don't know which one. It's safe to get elements from this list and cast them up to Parent
since, no matter what class the ?
represents, it's always safe to assume it's a Parent
subclass. So an argument of type List<? extends Parent>
can accept a List<Parent>
or a List<Child>
or a list of any subtype of Parent
. But we can't add elements to a List<? extends Parent>
because we don't know what type the ?
is, so we can never safely supply an argument to List.add
. This is called a covariant relationship, where it's safe to consider a list of a subclass to be a list of the superclass for these purposes.
The converse relationship is called contravariant. It's the exact same idea, but it's a little harder to understand sometimes. In Java, we would write it as List<? super Child>
. A List<? super Child>
is a list of some supertype of Child
. That ?
could be Parent
or it could be Object
or it could be some interface that Child
happens to implement, but it has to be a supertype of Child
. Now, we can never safely get elements from this list, since we have no idea what the type is. It's not Child
, since the list could be a List<Parent>
and could contain non-Child
instances. However, we can add elements to the list. Whatever the ?
is, it's a supertype of Child
, so we can always safely add Child
instances to List<? super Child>
, since the argument to add
can just be quietly upcast to the correct type.
Going back to your examples,
List<? extends Exception> lst = new ArrayList<Exception>();
The left-hand type is a list of some subtype of Exception
. Notably, ArrayList<Exception>
implements List<Exception>
and, since Exception
is (trivially) a subtype of itself, List<Exception>
is a valid List<? extends Exception>
.
List<NullPointerException> lst2 = new ArrayList<Exception>();
Here, ArrayList<Exception>
is still a subtype of List<Exception>
, but List<Exception>
is incompatible with List<NullPointerException>
, because in the absence of any variance annotations (extends
or super
), all generic arguments are assumed to be invariant (except arrays, but that's a whole other can of unsound worms). We could have written
List<? super NullPointerException> lst2 = new ArrayList<Exception>();
and gotten a write-only list of NullPointerException
, or we could have written
List<? extends Exception> lst2 = new ArrayList<NullPointerException>();
and gotten a read-only list of Exception
. But we have to specify which we want. Likewise,
public void f3(Collection<? extends S> c, List<S> l) {
c = l;
}
List<S>
implements Collection<S>
, and S
is (again, trivially) a subtype of S
, so we have Collection<S>
compatible with Collection<? extends S>
, hence the assignment compiles.
More useful reading material on when you want extends
vs. super
: What is PECS (Producer Extends Consumer Super)?