Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Understanding PECS for Wildcard Bounds

Welcome back! In our last lesson, we introduced upper-bounded (? extends T) and lower-bounded (? super T) wildcards. We saw that extends is useful when you need to read from a generic structure (a producer), and super is useful when you need to write to one (a consumer).

Today, we'll formalize this intuition into a simple yet powerful rule that will help you choose the correct wildcard every time. This lesson is dedicated to mastering the PECS principle, which stands for Producer Extends, Consumer Super. By the end of this lesson, you'll be able to confidently apply this principle to design flexible and type-safe generic APIs.

This principle was popularized by Joshua Bloch in his highly influential book, "Effective Java," and it's a cornerstone of high-quality Java library design.

The PECS Mnemonic: Producer Extends, Consumer Super

The PECS principle provides a straightforward guideline for using wildcards in method parameters. The name itself is the rule:

  • Producer Extends: If a parameterized type represents a producer—that is, you only read values from it—use a wildcard with an extends bound (? extends T).
  • Consumer Super: If a parameterized type represents a consumer—that is, you only write values to it—use a wildcard with a super bound (? super T).

Let's hear this explained by the man who made it famous, Joshua Bloch.

Effective Java - Still Effective After All These Years

In this classic talk, Joshua Bloch introduces the PECS mnemonic. His explanation is concise and provides the core intuition behind the rule.

Watch the clip from 24:25 to 25:37. Focus on the simple definition he provides for 'producer' and 'consumer'.

To help you remember this, here's a simple visual mnemonic. Think of "producer" as something that gives you items (arrow pointing out), and "consumer" as something that takes items from you (arrow pointing in).

PECS Principle in Java Generics
This image illustrates the PECS principle: A producer provides items (outward arrow), so you use `? extends T`. A consumer accepts items (inward arrow), so you use `? super T`.

Now, let's put this principle into practice with code.

Applying PECS: A Stack Example

Imagine you have a Stack<E> class and you want to add bulk operations: pushAll to push a collection of elements onto the stack, and popAll to pop all elements from the stack into a collection.

A naive implementation might look like this:

public class Stack<E> {
    // ... constructor and other methods
    
    public void pushAll(Collection<E> src) {
        for (E e : src) {
            push(e);
        }
    }
    
    public void popAll(Collection<E> dest) {
        while (!isEmpty()) {
            dest.add(pop());
        }
    }
}

This works, but it's not flexible. If you have a Stack<Number> and a Collection<Integer>, you can't call pushAll. Let's use PECS to fix this.

  1. Analyzing pushAll(Collection<E> src):

    • Question: Is the src collection a producer or a consumer from the stack's perspective?
    • Answer: The method reads elements from src to push them onto the stack. Therefore, src is a producer.
    • PECS rule: Producer Extends.
    • Solution: Change the signature to pushAll(Collection<? extends E> src).
  2. Analyzing popAll(Collection<E> dest):

    • Question: Is the dest collection a producer or a consumer?
    • Answer: The method writes elements popped from the stack into dest. Therefore, dest is a consumer.
    • PECS rule: Consumer Super.
    • Solution: Change the signature to popAll(Collection<? super E> dest).

Let's watch Joshua Bloch walk through this exact example.

Effective Java - Still Effective After All These Years

This segment demonstrates the practical application of PECS by refactoring the pushAll and popAll methods. It clearly shows the 'before' and 'after' and explains the benefits.

Watch from 25:37 to 28:09. Observe how applying PECS makes the API immediately more flexible, allowing you to push a Collection<Long> to a Stack<Number>.

The corrected, flexible API looks like this:

// Flexible bulk operations on a Stack
public void pushAll(Collection<? extends E> src) {
    for (E e : src) {
        push(e);
    }
}

public void popAll(Collection<? super E> dest) {
    while (!isEmpty()) {
        dest.add(pop());
    }
}

The Canonical Example: Collections.copy

The most famous application of PECS is in the Java Collections Framework itself. Look at the signature of the Collections.copy method:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

Let's analyze this using your new knowledge:

  • src (the source list): We read from it. It's a producer. So, its type is List<? extends T>.
  • dest (the destination list): We write to it. It's a consumer. So, its type is List<? super T>.

This signature allows for maximum flexibility. For example, you can copy a List<Integer> into a List<Number>.

List<Integer> ints = List.of(1, 2, 3);
List<Number> nums = new ArrayList<>();
nums.add(0.0);
nums.add(0.0);
nums.add(0.0);

// This is possible because `ints` is a List<? extends Number>
// and `nums` is a List<? super Integer>
Collections.copy(nums, ints); 
// nums is now [1, 2, 3]

For a more detailed breakdown with code examples, the Baeldung article provides an excellent walkthrough.

Java Generics PECS – Producer Extends Consumer Super

This article from Baeldung, 'Java Generics PECS', provides clear code examples for both the 'Producer Extends' and 'Consumer Super' sides of the rule.

Read sections 2 ('Producer Extends') and 3 ('Consumer Super'). The sendEmails and addUsersFromMarketingDepartment examples are great illustrations of the principle.

What if it's Both? Or Neither?

PECS covers the two most common cases, but what about the other possibilities?

Role of the Parameter Wildcard to Use In/Out
Producer only ? extends T In-parameter (reads from it)
Consumer only ? super T In-parameter (writes to it)
Both Producer & Consumer T (no wildcard) In/Out-parameter
Neither ? Irrelevant
  • Both Producer and Consumer: If you need to both read from and write to a collection within the same method, you cannot use a wildcard. You must use the exact type, e.g., List<T>. This makes the parameter invariant. An example would be a method that swaps the first and last elements of a list. You need to get() elements (produce) and set() an element (consume).

  • Return Types: As a rule of thumb, do not use wildcards in return types. Your method's users should not have to think about wildcards. Returning a List<T> is much easier to work with than a List<? extends T>.

Let's quickly watch the summary from the "Effective Java" talk, which neatly presents this in a table.

Effective Java - Still Effective After All These Years

This final clip provides a tabular summary of the PECS principle, covering all four scenarios: producer, consumer, both, and neither.

Watch from 32:55 to 35:36. Pay attention to the explanations for using no wildcard (invariant) and the unbounded wildcard (?).

Your Turn: Apply the Principle

Now it's your turn to design a method signature.

Challenge: Write the signature for a generic static method named union. This method should take two collections as input and return a new Set containing all elements that appear in either of the two input collections.

Think about the parameters. For each input collection: are you reading from it or writing to it? Based on that, what should the wildcard be?

Click here for the solution and explanation.

Since you are only reading from both input collections to create a new set, they are both producers.

According to the PECS principle (Producer Extends), the correct signature is:

import java.util.*;

public class SetUtils {
    public static <E> Set<E> union(Collection<? extends E> c1, Collection<? extends E> c2) {
        Set<E> result = new HashSet<>(c1);
        result.addAll(c2);
        return result;
    }
}

This signature allows you to find the union of a List<Integer> and a Set<Double> and store it in a Set<Number>, which a more restrictive signature like union(Collection<E> c1, Collection<E> c2) would not.

Conclusion

You've now mastered one of the most important best practices for working with Java Generics. By applying the PECS principle, you can create APIs that are both maximally flexible for your users and fully type-safe at compile time.

Key Takeaways:

  • PECS: Producer-Extends, Consumer-Super.
  • Use ? extends T for input parameters you only read from.
  • Use ? super T for input parameters you only write to.
  • If you need to both read and write, use the exact type T (no wildcard).
  • Avoid using wildcards in return types to keep your API simple for callers.

In our next lesson, we will peel back another layer of generics and explore what happens to all this type information at runtime. We will discuss the concept of type erasure and demonstrate its effects on generic code, a topic that is crucial for understanding the limitations and behaviors of generics in the JVM.

Can't find a good explanation? Sign up and we'll make it for you

Sign up