Explain bounded wildcards and the PECS rule. When do you use `? extends T` versus `? super T`?
answer
- PECS: Producer Extends, Consumer Super
- Generics are INVARIANT (List<Integer> is not List<Number>)
- ? extends T = read-only producer (no add except null)
- ? super T = write-only consumer (read back only as Object)
- Collections.copy(dest super, src extends); no wildcards in return types
basics
~20 sPECS means 'Producer Extends, Consumer Super.' If a parameter only produces (you read from) T values, use ? extends T. If it only consumes (you write into it) T values, use ? super T. This makes APIs flexible while staying type-safe.
solid answer
~50 sGenerics are invariant: a `List<Integer>` is not a `List<Number>`, which makes rigid APIs. **Bounded wildcards** restore flexibility. PECS — "Producer Extends, Consumer Super" — tells you which to pick by the role the parameter plays. If the parameter is a **producer** you only read T-values out of, use `? extends T`: you can read each element as a T but cannot add (except null), because the real element type is some unknown subtype. If it's a **consumer** you only write T-values into, use `? super T`: you can add a T (or any subtype) but reads come back only as Object. `Collections.copy(List<? super T> dest, List<? extends T> src)` is the canonical example — src produces, dest consumes. The payoff: callers can pass `List<Integer>` *and* `List<Double>` to a `Number` API. Don't use a wildcard on a return type — it forces wildcards onto callers. If a parameter is both producer and consumer, use an exact type.
code
java · 21 lines// PECS in action: src produces, dest consumes
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
T value = src.get(i); // read a T from the producer
dest.set(i, value); // write a T into the consumer
}
}
List<Number> dest = new ArrayList<>(List.of(0, 0, 0));
List<Integer> src = List.of(1, 2, 3);
copy(dest, src); // compiles: copy Integers into a Number list
// ? extends is read-only:
List<? extends Number> producer = src;
Number n = producer.get(0); // OK — read as Number
// producer.add(5); // COMPILE ERROR — cannot add to a producer
// ? super is write-only-ish:
List<? super Integer> consumer = dest;
consumer.add(99); // OK — Integer fits
Object o = consumer.get(0); // reads come back only as Objectgo deeper
Know that generics are invariant and that ? extends/? super exist to make APIs more flexible; you aren't expected to design with them yet.
State the PECS mnemonic and pick ? extends T for read-only inputs and ? super T for write-only outputs in straightforward cases.
Apply PECS correctly in API design, explain the read/write restrictions each wildcard imposes, avoid wildcard return types, and recognize the Collections.copy / Comparator<? super T> idioms.
Set wildcard conventions for a public API surface, reason about invariance vs array covariance trade-offs, balance signature flexibility against caller readability, and know when an exact type or a fresh type parameter beats a wildcard.
## The problem: generics are invariant With arrays, `Integer[]` is an `Object[]` (arrays are *covariant*). Generics deliberately are **not**: `List<Integer>` is **not** a subtype of `List<Number>`, even though `Integer` is a subtype of `Number`. This is **invariance**, and it's intentional — it prevents a whole class of unsafe writes. But it makes APIs rigid: a method declared `void addAll(Collection<Number> c)` would reject a `List<Integer>`, which is annoying because reading Integers as Numbers is perfectly safe. ## Wildcards: `?` and bounded wildcards A **wildcard** `?` means "some unknown type." A **bounded wildcard** constrains that unknown: - **`? extends T`** — "some unknown type that is T or a subtype of T" (an *upper* bound). - **`? super T`** — "some unknown type that is T or a supertype of T" (a *lower* bound). `List<? extends Number>` accepts a `List<Integer>`, a `List<Double>`, a `List<Number>` — any list whose element type is Number or below. `List<? super Integer>` accepts a `List<Integer>`, a `List<Number>`, a `List<Object>` — any list that can safely hold an Integer. ## Why each one restricts you the way it does **`? extends T` — you can read, not write.** Given `List<? extends Number> in`, the compiler knows every element is *at least* a Number, so `Number n = in.get(0)` is safe. But it does **not** know the *exact* element type — it could be `List<Double>` — so it forbids `in.add(anything)` (except `null`), because adding an Integer to a `List<Double>` would corrupt it. An `extends`-wildcarded collection is a **producer**: data flows *out*. **`? super T` — you can write, not usefully read.** Given `List<? super Integer> out`, the compiler knows the list holds Integer or some supertype, so `out.add(42)` is always safe (an Integer fits anywhere up the chain). But reading gives back only `Object`, because the element type could be `Object`. A `super`-wildcarded collection is a **consumer**: data flows *in*. ## PECS: Producer Extends, Consumer Super Joshua Bloch's mnemonic from *Effective Java*. For a method parameter, ask: **what role does it play relative to the type T the method works with?** - It **produces** Ts (the method reads from it) → `? extends T`. - It **consumes** Ts (the method writes into it) → `? super T`. - It does **both** (read *and* write) → use the exact type `T`, no wildcard. The canonical example is `Collections.copy`: ```java public static <T> void copy(List<? super T> dest, List<? extends T> src) ``` `src` is the source you read from → producer → `extends`. `dest` is where you write → consumer → `super`. Now `copy(numbers, integers)` compiles: copy `List<Integer>` into `List<Number>`. Another: `Collection.addAll(Collection<? extends E> c)` — `c` is read-only here (a producer), so `extends`, letting you `numbers.addAll(integerList)`. ## The payoff and the rules of thumb - Wildcards make an API accept the **widest** set of caller types safely. Without them, callers get spurious compile errors on perfectly safe operations. - **Don't put wildcards in return types.** `List<? extends Number> getThings()` forces every caller to deal with wildcards, which is contagious and unpleasant. Return a concrete parameterized type. - **`Comparable`/`Comparator` are always consumers**, so they're idiomatically `Comparable<? super T>` / `Comparator<? super T>` — e.g. `max(List<? extends T> list)` where `<T extends Comparable<? super T>>`. - If a type parameter appears only once in a method and you don't need to refer to it, you can often replace it with a wildcard for a simpler signature. ## Mental shortcut Upper-bounded (`extends`) = a **window you look through** (read-only out). Lower-bounded (`super`) = a **funnel you pour into** (write-only in). Producer → Extends, Consumer → Super.
- Why can't you add elements to a List<? extends Number> (other than null)?Because the compiler only knows the element type is *some* subtype of Number — it might be a List<Double>. Adding an Integer would corrupt a List<Double>. Since the exact type is unknown, no concrete value is provably safe to add, so only `null` (a member of every type) is allowed.
- Why is putting a wildcard in a return type discouraged?A wildcard return type forces every caller to handle the wildcard — they can't assign it to a clean parameterized type and the wildcard 'leaks' through their code. Wildcards belong on method *parameters* to widen what callers can pass; return concrete types so callers get a usable result.
- What do you use if a parameter is both read from and written to?An exact (invariant) type parameter, no wildcard — e.g. `List<T>`. A wildcard would forbid either the reads or the writes. PECS only applies to parameters that play a single role; a read-AND-write parameter needs the precise type.
Think of ? extends T as a vending machine window: you can take a drink (read), but you can't push your own drink back in. ? super T is a mailbox slot: you can drop letters in (write), but you can't reach in and identify what's already there beyond 'it's mail (Object).'
saying these in an interview costs you the question
- Swapping the rule — saying producers take `super` and consumers take `extends`.
- Thinking you can add arbitrary elements to a `? extends T` collection — only null is allowed.
- Believing `List<Integer>` is a subtype of `List<Number>` — generics are invariant.
- Putting wildcards on return types as a default.
- Forgetting that a read-and-write parameter must use an exact type, not a wildcard.