Generics

Wildcards: ? super (Lower Bounds)

15 min Lesson 6 of 13

Wildcards: ? super (Lower Bounds)

In the previous lesson you saw ? extends T, which lets you read from a collection safely. This lesson covers the mirror image: ? super T, the lower-bounded wildcard. Where ? extends is for producers (things that give you data), ? super is for consumers (things that accept data you write into them). Together they form the PECS principle — one of the most practically useful rules in Java generics.

The Problem: Writing Into a Generic Collection

Suppose you want to write a method that fills a list with integers generated by a counter. Your first instinct might be:

public static void fillWithIntegers(List<Number> list, int count) { for (int i = 0; i < count; i++) { list.add(i); } }

That compiles, but it is too restrictive. You cannot pass a List<Object> to it, even though an Object list can obviously hold integers. Nor can you pass a List<Number> when the caller happens to have a List<Object>. The fix is a lower bound:

public static void fillWithIntegers(List<? super Integer> list, int count) { for (int i = 0; i < count; i++) { list.add(i); // safe: Integer fits into any super-type of Integer } }

Now the method accepts List<Integer>, List<Number>, List<Object> — any list whose element type is Integer or any ancestor of Integer in the type hierarchy.

Why Writes Are Safe With ? super

The compiler's reasoning: if the list holds some type that is a supertype of Integer, then putting an Integer into it is always valid — because an Integer IS-A Number IS-A Object. The type system guarantees the assignment is safe regardless of which concrete supertype the list actually uses.

Why you cannot safely read with ? super: When you read from a List<? super Integer>, all the compiler knows is that each element is some unknown supertype of Integer — it could be Number, Object, or anything else. The only safe read type is Object, which loses all useful information. That is why lower-bounded wildcards are paired with write (consume) operations, not read (produce) operations.

A Concrete, Runnable Example

import java.util.ArrayList; import java.util.List; public class LowerBoundDemo { // Adds the first n non-negative integers into any list // that can hold Integer or one of its supertypes public static void fill(List<? super Integer> dest, int n) { for (int i = 0; i < n; i++) { dest.add(i); } } public static void main(String[] args) { List<Integer> intList = new ArrayList<>(); List<Number> numList = new ArrayList<>(); List<Object> objList = new ArrayList<>(); fill(intList, 3); // OK: Integer super Integer fill(numList, 3); // OK: Number super Integer fill(objList, 3); // OK: Object super Integer System.out.println(intList); // [0, 1, 2] System.out.println(numList); // [0, 1, 2] System.out.println(objList); // [0, 1, 2] // This would NOT compile — Double is NOT a supertype of Integer: // List<Double> dblList = new ArrayList<>(); // fill(dblList, 3); // compile error } }

PECS: Producer Extends, Consumer Super

The acronym PECS (coined by Joshua Bloch in Effective Java) gives you a simple rule to decide which wildcard to use:

  • If a parameter produces values that you will read out of it, use ? extends T.
  • If a parameter consumes values that you will write into it, use ? super T.
  • If you both read and write, use the exact type T with no wildcard.

A classic example that combines both wildcards in one method — copying elements from a source list into a destination list:

public static <T> void copy(List<? extends T> src, List<? super T> dest) { for (T element : src) { // read from producer src dest.add(element); // write into consumer dest } }

Read the signature aloud: "copy from a list that produces Ts (or subtypes) into a list that consumes Ts (or supertypes)." That is exactly what PECS says.

When in doubt, think about the direction of data flow. Data flows out of a producer (extends) and into a consumer (super). If you can decide which direction, you can always pick the right wildcard.

Real-World Usage in the JDK

The JDK itself applies PECS throughout java.util. Look at Collections.sort:

public static <T extends Comparable<? super T>> void sort(List<T> list) { ... }

The bound Comparable<? super T> means the comparator only needs to be defined on some ancestor of T — it does not have to be re-declared on every subclass. For example, if Dog extends Animal and Animal implements Comparable<Animal>, you can still sort a List<Dog> because Animal is a supertype of Dog and already provides compareTo.

Common Mistakes

  • Reading with ? super and expecting a specific type: The only guaranteed return type is Object. If you need the specific type, you will need a cast (which defeats type safety) or a different design.
  • Using ? super when the type parameter appears on both sides: If you read and write through the same parameter, use a plain type variable, not a wildcard.
  • Confusing lower bound with lower limit: ? super Integer means Integer or higher up the hierarchy (Number, Object) — not a smaller/more specific type. The word "super" refers to supertypes.
You cannot write a subtype into a lower-bounded wildcard if it is not the declared bound. For example, in a List<? super Number> you can add a Double (since Double extends Number) but you cannot safely add an arbitrary Object — the compiler will reject it because Object is not guaranteed to be a Number.

Summary

Lower-bounded wildcards (? super T) make a method accept any list whose element type is T or an ancestor of T, enabling safe writes. Remember PECS: use ? extends when a collection is a producer (you read from it) and ? super when it is a consumer (you write into it). The JDK's own collection utilities are built on exactly this principle.