What Is PECS in Java? Producer Extends, Consumer Super - Quiz

Total: 7 questions

1. 

What is the PECS principle in Java?

PECS (Producer Extends, Consumer Super) is the rule for choosing the bound of a wildcard in a generic method parameter. The choice depends on the role of the parameter in the method:

  • the parameter is a producer (a source), the method only reads T values from it → <? extends T>;
  • the parameter is a consumer (a destination), the method only writes T values into it → <? super T>;
  • the method both reads and writes → the exact type T with no wildcard.

Short form: take out — extends, put in — super. The name was coined by Joshua Bloch in “Effective Java” (3rd edition, Item 31). Example: sum(List<Number>) does not accept a List<Integer> because generic types are invariant, while sum(List<? extends Number>) does — the list is only read.

2. 

Which lists can be passed to a List<? extends Employee> parameter and to a List<? super Employee> parameter, and what can the method do with them?

For the hierarchy Person ← Employee ← Manager:

  • List<? extends Employee> (upper bounded, producer) accepts List<Employee> and List<Manager>. get() returns Employee, and the only value you can add is null: add(new Employee()) fails with incompatible types: Employee cannot be converted to CAP#1, because the argument may be a List<Manager>.
  • List<? super Employee> (lower bounded, consumer) accepts List<Employee>, List<Person>, List<Object>. You can add Employee, Manager and null, but get() returns only Object — an instanceof check is needed after that.
  • List<?> accepts any list, reads elements as Object and allows adding only null. That is the difference from List<Object>, which accepts any element but cannot receive a List<String>.
3. 

Why can you pass a List<Person> to a List<? super Employee> parameter but cannot call add(new Person()) on it?

This is not an exception to PECS but two consequences of the same bound. The compiler allows only operations that are safe for any list that may stand behind ? super Employee: List<Employee>, List<Person> or List<Object>.

  • add(new Employee()) and add(new Manager()) are safe: such an object is an Employee, a Person and an Object at the same time.
  • add(new Person()) would fit a List<Person>, but if a List<Employee> was passed, it would receive an object that is not an Employee. The compiler does not know which list was passed, so it rejects the call.

The same bound applies when passing an argument, when assigning (List<? super Employee> list = persons; compiles, = managers; does not) and when adding elements. So the claim “PECS works only when assigning collections and does not apply to adding elements” is wrong.

4. 

How do you apply PECS in your own method that moves elements from one list to another?

PECS is applied to each parameter separately: the source gets extends, the destination gets super.

// candidates is a producer, staff is a consumer
static void hireAll(List<? extends Employee> candidates, List<? super Employee> staff) {
    for (Employee e : candidates) {
        staff.add(e);
    }
}

List<Manager> managers = List.of(new Manager(), new Manager());
List<Person> persons = new ArrayList<>();
hireAll(managers, persons);
System.out.println(persons.size()); // 2

If both parameters were declared as List<Employee>, the call hireAll(managers, persons) would not compile because of either argument. With wildcards the method accepts the widest range of type combinations and stays type-safe.

5. 

How is the PECS principle reflected in the signatures of the Java standard library?

JDK methods that read from one parameter and write to another are declared by PECS:

  • Collections.copy(List<? super T> dest, List<? extends T> src) — src is the producer, dest the consumer;
  • Collections.addAll(Collection<? super T> c, T... elements) — the collection is a consumer;
  • Collection.addAll(Collection<? extends E> c) — the argument is a producer;
  • List.sort(Comparator<? super E> c) — the comparator consumes the elements;
  • Collections.max(Collection<? extends T> coll, Comparator<? super T> comp) — the collection is a producer, the comparator a consumer.

A Comparator<T> receives objects in compare(), so it consumes them and is declared as ? super T. Thanks to that, a single Comparator<Object> such as Comparator.comparing(Object::toString) also sorts a List<Integer>: [10, 9, 100] becomes [10, 100, 9] (string comparison).

6. 

Why does Collections.copy() fail with an empty destination list even though the types match by PECS?

PECS is about types, not size. Collections.copy(dest, src) replaces elements of dest by index and never adds new ones: if dest.size() < src.size(), it throws IndexOutOfBoundsException: Source does not fit in dest. If dest is longer, its tail stays: copying [3, 1, 2] into the List<Number> [0, 0, 0, 0.5] gives [3, 1, 2, 0.5].

To copy into an empty list, use dest.addAll(src) or new ArrayList<>(src).

7. 

What are the most common mistakes with PECS and wildcards?

  • Expecting List<Manager> to be a subtype of List<Employee>. Generic types are invariant; declare a source parameter as List<? extends Employee>.
  • Adding to a List<? extends T> — the error is cannot be converted to CAP#1. If the method writes to the collection, use ? super T or the exact T.
  • Treating ? extends as protection against changes: remove(), clear() and add(null) still work. For immutability use List.copyOf() or Collections.unmodifiableList().
  • Reading from a List<? super T> as T — the element type is Object. If you need to both read as T and write, use the exact type List<T>.
  • Putting a wildcard in a return type (List<? extends Number> load()): the caller cannot add anything to the result. Use wildcards only in parameters.
  • Trying to give a wildcard two bounds: <? extends A super B> does not compile — a wildcard has exactly one bound.
Page 1 of 1