Learning goals
Write type-safe collections and APIs with generics, understand type erasure and bounds, use wildcards (? extends, ? super) for flexible method signatures, and avoid raw types.
Why generics?
Without generics, collections hold Object—casts and ClassCastException at runtime:
List list = new ArrayList();
list.add("hi");
Integer n = (Integer) list.get(0); // blows up at runtime
With generics, errors move to compile time:
List<String> names = new ArrayList<>();
names.add("Ada");
// names.add(42); // compile error
String s = names.get(0);
Generic classes and methods
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T get() {
return value;
}
}
public static <T> T first(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
Type parameter T is placeholder replaced by concrete type at use site.
Diamond operator
Map<String, List<Integer>> map = new HashMap<>();
Compiler infers type args on right side (JDK 7+).
Bounded type parameters
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
public class NumberBox<T extends Number> {
private T value;
public double doubleValue() {
return value.doubleValue();
}
}
extends upper bound; super lower bound on wildcards (below).
Multiple bounds: <T extends Number & Comparable<T>>.
Wildcards
Producer extends, consumer super (PECS):
public static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) {
total += n.doubleValue();
}
return total;
}
public static void addIntegers(List<? super Integer> dest) {
dest.add(42);
}
| Wildcard | Reads (producer) | Writes (consumer) |
|---|---|---|
? extends T |
Yes (as T) | No (except null) |
? super T |
As Object | Yes (add T) |
? |
As Object | No |
Use List<T> when both read and write same type; wildcards for API flexibility.
Type erasure
Generics exist at compile time; bytecode uses raw types and casts. Implications:
- No
new T(),T[], orinstanceof T. - Overloads differing only by type parameter erasure collide.
Class<String>vsClass<Integer>— same runtime classClass.
Bridge methods preserve polymorphism for generics overrides.
Raw types (avoid)
List raw = new ArrayList(); // legacy, unchecked warnings
Fix with proper type argument or List<?> if truly unknown.
Generic records and sealed types
record Pair<A, B>(A first, B second) {}
public sealed interface Result<T> permits Success, Failure { }
public record Success<T>(T value) implements Result<T> { }
public record Failure<T>(String error) implements Result<T> { }
Modern Java uses generics throughout standard library (Optional<T>, Stream<T>, Map<K,V>).
Common mistakes
- Using raw
List— loses safety; enable-Xlint:unchecked. List<Object>vsList<?>— former accepts any add; latter read-focused unknown type.- Arrays and generics —
new List<String>[10]illegal; useList[]only with care orArrayList. - Over-generic APIs —
public <T> T process(T input)without need adds noise. - Ignoring PECS — inflexible methods or unsafe casts.
Practice checkpoint
-
Declare
Map<String, Integer>word counts variable.- Answer:
Map<String, Integer> counts = new HashMap<>();
- Answer:
-
Why
List<Object>not acceptList<String>assignment for adding?- Answer: Would allow inserting non-String into String list via Object view; generics invariant.
-
Write method copying numbers from
List<? extends Number>toList<? super Number>.- Answer: Loop read from source,
dest.add(num)if dest typedsuper Number.
- Answer: Loop read from source,
-
Can you do
if (obj instanceof List<String>)?- Answer: No—erasure; use
instanceof List<?>then cast with unchecked warning or pattern.
- Answer: No—erasure; use
Interview angles
- “Erasure—why?” — Backward compatibility with pre-generics bytecode; trade-off vs reified generics (C#).
- “PECS rule?” — Producer extends, consumer super.
- “Generic array creation?” — Illegal due to erasure and heap pollution risk.