13. Generics and Wildcards
Generics make collections and methods **type-safe and reusable**. This lecture covers generic classes / methods, the wildcards `?`, `? extends`, `? super`, and the limits caused by type erasure.
What you'll learn
- 1Declare a generic class or method
- 2Apply the wildcards `? extends T`, `? super T`
- 3Understand PECS (Producer Extends, Consumer Super)
- 4Understand the limits of type erasure
Overview
Generics make collections and methods **type-safe and reusable**. This lecture covers generic classes / methods, the wildcards `?`, `? extends`, `? super`, and the limits caused by type erasure.
Core Concepts
1) Generic class
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
Box<String> b = new Box<>();
b.set("hello");
String s = b.get(); // no cast needed2) Generic method
static <T> T firstOf(java.util.List<T> list) {
return list.isEmpty() ? null : list.get(0);
}3) Wildcards
List<? extends Number> nums = List.of(1, 2.0, 3L);
double sum = 0;
for (Number n : nums) sum += n.doubleValue(); // read OK
// nums.add(4); // compile error β write forbidden**`? extends T`** = read-only producer. **`? super T`** = write-only consumer.
4) PECS
**P**roducer **E**xtends, **C**onsumer **S**uper: if you only read use `extends`; if you only write use `super`.
5) Type erasure
At runtime, generic type information is **erased**. `List<String>` and `List<Integer>` are the same class at runtime, so `instanceof List<String>` is illegal.
Examples
Example 1 β `BoxDemo.java`
public class BoxDemo {
static class Box<T> {
private T value;
void set(T v) { value = v; }
T get() { return value; }
}
public static void main(String[] args) {
Box<String> sb = new Box<>();
sb.set("hello");
System.out.println(sb.get());
Box<Integer> ib = new Box<>();
ib.set(42);
System.out.println(ib.get());
}
}**Output**
hello
42Example 2 β `FirstOf.java`: generic method
import java.util.List;
public class FirstOf {
static <T> T firstOf(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
public static void main(String[] args) {
System.out.println(firstOf(List.of("a", "b")));
System.out.println(firstOf(List.of(1, 2, 3)));
}
}**Output**
a
1Example 3 β `PecsDemo.java`
import java.util.ArrayList;
import java.util.List;
public class PecsDemo {
static double sumOf(List<? extends Number> nums) { // producer
double s = 0;
for (Number n : nums) s += n.doubleValue();
return s;
}
static void addInts(List<? super Integer> sink) { // consumer
sink.add(1);
sink.add(2);
}
public static void main(String[] args) {
List<Integer> ints = List.of(1, 2, 3);
System.out.println(sumOf(ints));
List<Number> dest = new ArrayList<>();
addInts(dest);
System.out.println(dest);
}
}**Output**
6.0
[1, 2]Example 4 β `Erasure.java`
import java.util.ArrayList;
import java.util.List;
public class Erasure {
public static void main(String[] args) {
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass()); // true!
}
}**Output**
true**Note:** both are `ArrayList` at runtime β the generic parameter is erased.
Common Mistakes
- Trying `new T[10]` β illegal because of erasure
- Using `instanceof List<String>` β illegal
- Writing to a `List<? extends T>` (allowed reads only)
- Reading a specific type from `List<? super T>` (you can only read as `Object`)
- Using raw types (`List`) instead of parameterized types β loses type-safety warnings
Summary
- Generics give you type safety at compile time
- PECS: producer β `extends`, consumer β `super`
- Type erasure means runtime type info is lost
Practice
# Practice - 13. Generics
## Exercise 1 β `Pair<A, B>`
- File: `Homework01.java`
- Key concepts: two type parameters
Requirements
- Class `Pair<A, B>` with `A first`, `B second`, and a `toString()` returning `(a, b)`.
- Create `Pair<String, Integer>("Jisoo", 21)` and print it.
Expected output
(Jisoo, 21)## Exercise 2 β `sumOf` with `? extends Number`
- File: `Homework02.java`
- Key concepts: bounded wildcard
Requirements
- Static `double sumOf(List<? extends Number> list)`.
- Call with `List<Integer>` and `List<Double>` lists.
Expected output
ints=6.0
doubles=4.5## Solutions After trying it yourself, compare with [`answer/`](./answer/).
Solution code (homework/answer/)
answer/Homework01.java
/** Pair<A, B>. */
public class Homework01 {
static class Pair<A, B> {
A first; B second;
Pair(A a, B b) { this.first = a; this.second = b; }
@Override public String toString() { return "(" + first + ", " + second + ")"; }
}
public static void main(String[] args) {
Pair<String, Integer> p = new Pair<>("Jisoo", 21);
System.out.println(p);
}
}
answer/Homework02.java
import java.util.List;
/** Sum of numbers using bounded wildcard. */
public class Homework02 {
static double sumOf(List<? extends Number> list) {
double s = 0;
for (Number n : list) s += n.doubleValue();
return s;
}
public static void main(String[] args) {
System.out.println("ints=" + sumOf(List.of(1, 2, 3)));
System.out.println("doubles=" + sumOf(List.of(1.5, 3.0)));
}
}
Try It Yourself
cd 03_collections/13_generics/src
javac BoxDemo.java
java BoxDemoNext Lecture
[14_Stream](../14_μ€νΈλ¦Ό_API/) β declarative collection operations.
All lecture materials and example code are openly available on GitHub.
View on GitHub β