Lesson Completion
Back to course

Wildcards and PECS: Flexible Generic APIs

Advanced
15 minutesβ˜…4.6Java

1. The Hook (The "Byte-Sized" Intro)

  • In a Nutshell: Wildcards (?) represent unknown types in generics.
  • Three types: Unbounded (<?>), Upper bounded (<? extends T> - read-only, "Producer"), Lower bounded (<? super T> - write-only, "Consumer").
  • PECS principle: Producer Extends, Consumer Superβ€”use extends when reading, super when writing. This enables flexible APIs like Collections.copy(List<? super T> dest, List<? extends T> src).

Think of USB ports. Unbounded = any USB device. Upper bounded = only USB storage (can READ). Lower bounded = accepts any power input (can WRITE power)!


2. Conceptual Clarity (The "Simple" Tier)

πŸ’‘ The Analogy: The Library Borrowing System

  • <? extends Book>: Lending library (read books, can't addβ€”might break genre rules)
  • <? super Book>: Donation box (add books, don't know exact type to read)

3. Technical Mastery (The "Deep Dive")

Three Wildcard Types

WildcardSyntaxCan Read?Can Write?Use Case
Unbounded<?>Yes (as Object)No (unsafe)Unknown type
Upper bounded<? extends T>Yes (as T)NoProducer (read from)
Lower bounded<? super T>No (unknown)Yes (as T)Consumer (write to)

PECS Principle

  • Producer Extends: If you're getting items OUT β†’ use <? extends T>
  • Consumer Super: If you're putting items IN β†’ use <? super T>

4. Interactive & Applied Code

java
import java.util.*; public class WildcardDemo { // UPPER BOUNDED: Producer (read from list) public static double sumNumbers(List<? extends Number> list) { double sum = 0; for (Number num : list) { // βœ… Can READ as Number sum += num.doubleValue(); } // list.add(5); // ❌ Cannot WRITE (might be List<Double>!) return sum; } // LOWER BOUNDED: Consumer (write to list) public static void addNumbers(List<? super Integer> list) { list.add(1); // βœ… Can WRITE Integers list.add(2); list.add(3); // Integer num = list.get(0); // ❌ Cannot READ as Integer (might be List<Object>!) } // PECS in action: Collections.copy public static <T> void copy( List<? super T> dest, // Consumer Super (write to) List<? extends T> src // Producer Extends (read from) ) { for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } } // UNBOUNDED: Unknown type public static void printList(List<?> list) { for (Object obj : list) { // Can read as Object System.out.println(obj); } // list.add("X"); // ❌ Cannot add (unsafe!) } public static void main(String[] args) { // UPPER BOUNDED EXAMPLE List<Integer> ints = Arrays.asList(1, 2, 3); List<Double> doubles = Arrays.asList(1.1, 2.2); System.out.println("Sum ints: " + sumNumbers(ints)); System.out.println("Sum doubles: " + sumNumbers(doubles)); // LOWER BOUNDED EXAMPLE List<Number> numbers = new ArrayList<>(); List<Object> objects = new ArrayList<>(); addNumbers(numbers); // βœ… Number super Integer addNumbers(objects); // βœ… Object super Integer // addNumbers(ints); // ❌ Integer not super Integer System.out.println("Numbers: " + numbers); System.out.println("Objects: " + objects); // PECS EXAMPLE List<Number> dest = new ArrayList<>(Arrays.asList(0, 0, 0)); List<Integer> src = Arrays.asList(10, 20, 30); copy(dest, src); // dest consumes, src produces System.out.println("Copied: " + dest); // UNBOUNDED List<String> strings = Arrays.asList("A", "B", "C"); printList(strings); printList(ints); // Invariance demo demonstrateInvariance(); } static void demonstrateInvariance() { List<Integer> intList = new ArrayList<>(); // List<Number> numList = intList; // ❌ Generics are INVARIANT! // But wildcards allow this: List<? extends Number> numList = intList; // βœ… OK with wildcard Number num = numList.get(0); // βœ… Can read // numList.add(5); // ❌ Cannot write } }

5. The Comparison & Decision Layer

Decision Tree: Choosing Wildcards

graph TD A{What are you doing?} A -- Reading from collection --> B[Use <? extends T>] A -- Writing to collection --> C[Use <? super T>] A -- Both reading & writing --> D[Use <T> no wildcard] A -- Don't care about type --> E[Use <?>]

6. The "Interview Corner" (The Edge)

The "Killer" Interview Question: "Why can't you add elements to List<? extends Number>?" Answer: Because the exact type is unknown! It could be List<Integer>, List<Double>, etc.

java
List<? extends Number> list = new ArrayList<Integer>(); // list.add(5); // ❌ Might be List<Double>! // list.add(3.14); // ❌ Might be List<Integer>! // list.add(new Object()); // ❌ Definitely not Number subtype! //Java prevents ALL adds to maintain type safety

Exception: null is allowed (it's any type).

Pro-Tip: PECS from Effective Java:

java
// ❌ BAD: Inflexible public void processItems(List<Number> numbers) { // Can ONLY pass List<Number>, not List<Integer>! } // βœ… GOOD: Producer Extends (reading from list) public void processItems(List<? extends Number> numbers) { // Accepts List<Integer>, List<Double>, etc. for (Number n : numbers) { System.out.println(n.doubleValue()); } } // βœ… GOOD: Consumer Super (writing to list) public void addDefaults(List<? super Integer> list) { list.add(0); list.add(-1); }

PECS makes APIs maximally flexible!

Topics Covered

Java FundamentalsAdvanced Java

Tags

#java#generics#type-safety#reusability

Last Updated

2025-02-01