Dataslope logoDataslope

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 of List<Animal>, even though Dog is a subtype of Animal.

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);             // ClassCastException

The 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.

Code Block
Java 8 (Update 492)

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):

Code Block
Java 8 (Update 492)

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:

Code Block
Java 8 (Update 492)

? 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):

Code Block
Java 8 (Update 492)

? 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>T must be comparable to itself or any supertype. (Why ? super T? Because if Animal implements Comparable<Animal>, then a Dog is "comparable enough" — it inherits the comparison from Animal.)
  • Collection<? extends T> — the collection produces Ts. We read; we never write into it.
Code Block
Java 8 (Update 492)

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.

Code Block
Java 8 (Update 492)

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

Challenge
Java 8 (Update 492)
Apply PECS to addAll

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 is T or 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

QuestionSelect one

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

QuestionSelect one

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

QuestionSelect one

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

On this page