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:
- Why are generics invariant?
- What is
? extends? - What is
? super? - What is PECS?
- 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 aList<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.
| Role | Wildcard | I can |
|---|---|---|
| Producer (read) | ? extends T | get T, not add |
| Consumer (write) | ? super T | add T, get Object |
| Both read and write | no 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. AConverter<Integer, String>is not aConverter<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 aJpaRepository<Order, Long>(and should not be —savewould 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>orList<? super Order>depending on whether I insertOrderinstances.
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