Skip to main content

Week 5 Day 4 — Wildcards and PECS

Goal

Today I want wildcards as flexibility at the use site, with PECS as the rule I can say in an interview.

Main questions:

  1. Why are generics invariant?
  2. What is ? extends?
  3. What is ? super?
  4. What is PECS?
  5. When do I use ? unbounded?

1. Invariance

List<Integer> ints = new ArrayList<>();
// List<Number> nums = ints; // does not compile

If that assignment were legal, nums.add(3.14) would put a Double into an Integer list. Generics refuse the assignment. Invariant: List<Integer> is not a List<Number>, even though Integer is-a Number.

I still need a method that reads numbers from a list of integers or doubles. That is a wildcard.

Memory sentence:

List<Integer> is not a List<Number>. Wildcards express “a list of some subtype (or supertype).”


2. Producer — ? extends

void sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
// numbers.add(1); // illegal (except null)
}

sum(List.of(1, 2, 3)); // List<Integer>
sum(List.of(1.0, 2.0)); // List<Double>

List<? extends Number> is a list of some unknown subtype of Number. I can get Number. I cannot add (except null) because the compiler does not know whether the list is Integer or Double.

This is a producer: it produces values I read.


3. Consumer — ? super

void addInts(List<? super Integer> target) {
target.add(1);
target.add(2);
Object o = target.get(0); // only Object — the list might be List<Object>
}

addInts(new ArrayList<Integer>());
addInts(new ArrayList<Number>());
addInts(new ArrayList<Object>());

List<? super Integer> is a list of some unknown supertype of Integer. I can add Integer (and null). I cannot read Integer back; get is Object.

This is a consumer: it accepts values I write.


4. PECS

Producer Extends, Consumer Super.

RoleWildcardI can
Producer (read)? extends Tget T, not add
Consumer (write)? super Tadd T, get Object
Both read and writeno wildcard: List<T>get and add T
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}

src produces T. dest consumes T. That is the textbook signature (Collections.copy).

If I both add and get Order, I want List<Order>, not a wildcard.

Memory sentence:

PECS: producer extends, consumer super. If I read and write, I want List<T>.


5. Unbounded ?

List<?> is a list of some unknown type. I can get Object. I can add only null. It is useful when I only care about size, isEmpty, or passing the list through.

List<Object> is different: I may add any object. List<?> does not let me add a String.


6. Spring connection

  • Converter<S, T> is invariant in both parameters. A Converter<Integer, String> is not a Converter<Number, String>.
  • Event listeners: ApplicationListener<? extends ApplicationEvent> is a producer-style bound so a listener for a subtype still fits a registry of listeners.
  • JpaRepository<T, ID> stays invariant: JpaRepository<VipOrder, Long> is not a JpaRepository<Order, Long> (and should not be — save would break).
  • When I write a helper that reads entities, List<? extends Order> is the right parameter. When it saves a list of orders, List<Order> or List<? super Order> depending on whether I insert Order instances.

7. Common traps

Trap 1: Using List<Number> as the parameter “so Integer works.” Callers with List<Integer> cannot pass it. Use List<? extends Number>.

Trap 2: ? extends and then add. The compiler blocks it. That is the feature.

Trap 3: List<Object> when I meant List<?>.

Trap 4: Wildcards on return types as a habit. Returning List<? extends Order> makes the caller unable to add. Return List<Order> unless I truly want to hide the concrete element type.

Trap 5: Reciting PECS without saying which side of copy is which.


Practice Questions and Answers

Question 1

Why is List<Integer> not a List<Number>?

Answer:

If it were, I could add a Double through the Number view and break the integer list. Generics are invariant. For reading numbers I use List<? extends Number>.


Question 2

What is PECS?

Answer:

Producer Extends, Consumer Super. A parameter I read from is ? extends T. A parameter I write to is ? super T. If I do both, I use List<T>.


Question 3

Why can I not add to List<? extends Number>?

Answer:

The actual list might be List<Integer> or List<Double>. The compiler does not know which, so the only safe add is null.


Question 4

List<?> vs List<Object>?

Answer:

List<Object> can hold any object; I can add a String. List<?> is a list of an unknown type; I cannot add a String. I use ? when I only inspect structure.


Question 5

When should a method return a wildcard type?

Answer:

Rarely. A wildcard return pushes PECS onto every caller. I return List<Order> (or List<T>) unless I am hiding a producer I do not want mutated.


Memory sentences

Generics are invariant. Wildcards add the flexibility arrays got from covariance.

PECS: producer extends, consumer super.

If I read and write, I want List<T>, not a wildcard.

Next: Week 5 Day 5 — Optional