Recommended resources
The freely available resources this lesson will direct you to.
- Generics and Wildcards in Java | Part 2 | Invariance vs Covariance vs Contravariance | Geekificwww.youtube.com
- Effective Java - Still Effective After All These Yearswww.youtube.com
- Java Generics PECS – Producer Extends Consumer Super | Baeldungwww.baeldung.com
- What is PECS (Producer Extends Consumer Super)?stackoverflow.com
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
extendsbound (? extends T). - Consumer Super: If a parameterized type represents a consumer—that is, you only write values to it—use a wildcard with a
superbound (? 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).
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.
-
Analyzing
pushAll(Collection<E> src):- Question: Is the
srccollection a producer or a consumer from the stack's perspective? - Answer: The method reads elements from
srcto push them onto the stack. Therefore,srcis a producer. - PECS rule: Producer Extends.
- Solution: Change the signature to
pushAll(Collection<? extends E> src).
- Question: Is the
-
Analyzing
popAll(Collection<E> dest):- Question: Is the
destcollection a producer or a consumer? - Answer: The method writes elements popped from the stack into
dest. Therefore,destis a consumer. - PECS rule: Consumer Super.
- Solution: Change the signature to
popAll(Collection<? super E> dest).
- Question: Is the
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 isList<? extends T>.dest(the destination list): We write to it. It's a consumer. So, its type isList<? 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 toget()elements (produce) andset()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 aList<? 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 Tfor input parameters you only read from. - Use
? super Tfor 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.