Bounded Types and Wildcards
Why List<Dog> is not a List<Animal>, what ? extends T and ? super T really mean, and the PECS rule that makes it all click
This page is the trickiest one in the course, and the one that most separates "I have used generics" from "I understand generics."
It centers on a single fact about Java generics:
List<Dog>is not a subtype ofList<Animal>, even thoughDogis a subtype ofAnimal.
That looks wrong at first glance. Once you understand why Java
designed it that way, everything else — ? extends T, ? super T,
PECS — falls out naturally.
The setup
Dog and Cat both extend Animal. So far, normal OOP.
Why List<Dog> is not List<Animal>
Imagine the rule were the other way — that List<Dog> was a
List<Animal>. Then this code would compile and crash:
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs; // pretend this were legal
animals.add(new Cat()); // adding a Cat to a List<Dog>!
Dog d = dogs.get(0); // ClassCastExceptionThe compiler will never allow that, so Java declares generic types
invariant: List<X> and List<Y> are not subtype-related, even
when X and Y are.
Invariance is safe — but it is also inflexible. Now we need a way to say "I just want to read numbers" or "I just want to write integers," without losing type safety. That is what wildcards give us.
The unbounded wildcard: ?
List<?> means "a list of something, but I do not know what." You
can read from it as Object, and you cannot write into it (except
null):
That's List<?>: maximally general, but read-only.
Upper-bounded: ? extends T (you can read T)
List<? extends Number> means "a list of some specific subtype of
Number." The compiler does not know which subtype, so it is
strict about writes — but reads are guaranteed to be a Number:
? extends T is what you use when your method only consumes the
collection — it just reads elements.
Lower-bounded: ? super T (you can write T)
List<? super Integer> means "a list of Integer, or any
supertype of Integer (e.g. Number, Object)." You can safely
add Integers to such a list, but reads come out as Object
(the compiler only knows the elements are some supertype of
Integer):
? super T is what you use when your method only produces values
(it writes into the collection).
The PECS rule
This is the most famous mnemonic in Java generics, coined by Josh Bloch:
Producer Extends, Consumer Super.
- If the collection produces values out to you (you read from it), use
? extends T.- If the collection consumes values from you (you write into it), use
? super T.- If it does both, use exactly
T(no wildcard).
The classic illustration is Collections.copy(dest, src):
public static <T> void copy(List<? super T> dest, List<? extends T> src)src is the producer (we read from it): ? extends T. dest is
the consumer (we write into it): ? super T.
Picture: variance on a number line
? extends T shrinks toward subtypes (read-only side). ? super T
shrinks toward supertypes (write-only side). Plain T is the fixed
point in the middle.
A more concrete example: a generic max
Collections.max is a real-world PECS exemplar. Its signature is
roughly:
public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)That's a mouthful, so let's decode it:
T extends Comparable<? super T>—Tmust be comparable to itself or any supertype. (Why? super T? Because ifAnimalimplementsComparable<Animal>, then aDogis "comparable enough" — it inherits the comparison fromAnimal.)Collection<? extends T>— the collection producesTs. We read; we never write into it.
A multi-file challenge of PECS
Here is the canonical exercise: write a method copyAll(dest, src)
that copies from one list to another, where the element types might
differ in the safe direction.
Try changing the signatures (e.g. swap extends and super) and see
what the compiler tells you. The error messages are a great way to
learn the variance rules.
When not to use wildcards
Wildcards are for method parameters. They are almost never the right thing for return types or fields, because they push the "I don't know the type" problem onto every caller.
// Bad return type — caller has nothing useful to do with it
public List<? extends Number> getNumbers() { ... }
// Good — be specific about what you produce
public List<Integer> getIntegers() { ... }Rule of thumb: wildcards in, concrete types out.
Practice
Write a class Bag<T> that wraps a List<T> and exposes:
- A constructor with no arguments.
void add(T item)— appends an item.void addAll(Iterable<? extends T> items)— copies all items from any iterable whose element type isTor a subtype (PECS: src is a producer, so? extends T).List<T> all()— returns the underlying list.
Expected output (when the provided Main runs):
[1, 2, 3, 10, 20]
Test your understanding
Why is List<Dog> not assignable to List<Animal>?
Because Dog and Animal are different classes
Because if it were, you could add a Cat to a List<Dog> via the wider reference, breaking type safety
Because Java doesn't support polymorphism for collections
Because Animal is abstract
What does PECS stand for, and when do you apply each side?
"Public, Encapsulated, Composed, Shared" — for OO design
"Producer Extends, Consumer Super" — use ? extends T for parameters you only read from, ? super T for parameters you only write into
"Performance, Encapsulation, Cohesion, Safety"
"Parameter Extends, Class Super" — applied to class declarations
Why is using List<? extends Number> as a return type generally a bad idea?
It is forbidden by the compiler
It pushes "I don't know the exact element type" onto every caller, while a concrete return type like List<Integer> is just as easy to produce
It causes runtime casts to fail
It is slower than List<Integer> at runtime