If we define a type like Collection
interface Collection
We can then plug in different things for E to get different types: for example, Collection<Animal> has methods Animal get() and void add(Animal e)
If we want to write generic code that works on any Collection of animals, we can't simply use Collection<Animal>, because a Collection<Dog> is not a Collection<Animal>. That's because we can call add(new Cat()) on a Collection<Animal>, but we can't on a Collection<Dog>.
Wildcards give us a common supertype that we can use for arguments to methods that should be able to operate on multiple kinds of collections.
Collection<? extends Animal> is a supertype of each type Collection<T> where T is a subtype of Animal. See board image for which methods are allowed.
Collection<? super Animal> is a supertype of each type Collection<T> where T is a supertype of Animal. See board image for which methods are allowed.
The way to think about subtype relationships between wildcard types is by thinking about what methods exist in a wildcard type, and then seeing if the proposed subtype methods are more specific than the proposed supertype.
For example, Collection<? extends Animal> has methods Animal get() but no add method at all. Collection<Dog> has a get method; it returns a Dog and a Dog is-an Animal, so this satisfies the specification of Collection<? extends Animal> so a Collection<Dog> is-a Collection<? extends Animal>.