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
| Wildcard | Syntax | Can Read? | Can Write? | Use Case |
|---|---|---|---|---|
| Unbounded | <?> | Yes (as Object) | No (unsafe) | Unknown type |
| Upper bounded | <? extends T> | Yes (as T) | No | Producer (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 safetyException: 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!