Topic 9. Generics
Generics in Java let you write reusable code without losing type safety. In practical terms, you define an algorithm once, and the compiler ensures that invalid types are rejected early. For beginners this is critical: generics reduce runtime failures and remove many unsafe casts.
1. Problem Generics Solve
Before generics, raw types were common. Code compiled, but failures appeared at runtime:
List list = new ArrayList();
list.add("Alice");
list.add(100);
String name = (String) list.get(1); // ClassCastException
With generics, invalid usage is blocked earlier:
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(100); // compile-time error
String first = names.get(0);
Core benefit: fail fast at compile time, not late in production.
2. Basic Syntax and Conventions
List<String> means “list of strings.” Map<String, Integer> means “string key, integer value.” Common type parameter names:
| Parameter | Typical meaning |
|---|---|
T | generic type |
E | collection element |
K / V | key / value |
R | result type |
Generic method example:
public static <T> void printAll(List<T> items) {
for (T item : items) {
System.out.println(item);
}
}
Generic class example:
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
3. extends, super, and PECS
Wildcards make APIs flexible:
public static double sum(List<? extends Number> nums) {
double total = 0;
for (Number n : nums) {
total += n.doubleValue();
}
return total;
}
public static void addDefaults(List<? super Integer> out) {
out.add(10);
out.add(20);
}
Meaning:
? extends Number: safe to read asNumber, restricted writes.? super Integer: safe to writeInteger, reads are usuallyObject.
PECS rule:
- Producer Extends for read-oriented sources.
- Consumer Super for write-oriented targets.
4. Why List<Integer> Is Not List<Number>
Java generics are invariant. List<Integer> is not a subtype of List<Number>:
List<Integer> ints = List.of(1, 2, 3);
// List<Number> nums = ints; // compile error
If this were allowed, Double could be inserted into an integer list.
5. Bounded Type Parameters
Sometimes a generic type must satisfy a contract:
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
Here T must be comparable to itself.
Multiple bounds are possible:
<T extends Number & Comparable<T>>
Class bound first, then interface bounds.
6. Type Erasure and Practical Impact
Java implements generics via type erasure. Checks happen at compile time, while many concrete type arguments are erased at runtime.
Consequences:
- You cannot do
new T(). - You cannot reliably use
instanceof List<String>. - You cannot create
new List<String>[10].
So generics are primarily a compile-time safety mechanism.
7. API Design with Generics
Narrow signature:
void copy(List<Object> dst, List<Object> src)
Reusable signature:
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (T item : src) {
dst.add(item);
}
}
Read-oriented helper:
public static void printNumbers(List<? extends Number> nums) {
nums.forEach(System.out::println);
}
Write-oriented helper:
public static void fillWithZeros(List<? super Integer> target, int n) {
for (int i = 0; i < n; i++) {
target.add(0);
}
}
8. Frequent Beginner Mistakes
- Raw types (
List list) instead of parameterized types. - Confusing
extendswithsuper. - Forcing unchecked casts to bypass compiler feedback.
- Overcomplicated generic signatures where simpler API would be clearer.
9. Practical Checklist
- Always parameterize collection types.
- Use
extendsfor read contracts. - Use
superfor write contracts. - Keep signatures readable.
- Add short Javadoc examples for non-trivial generic APIs.
Key Takeaway
Generics are not syntax decoration. They are a reliability tool: fewer runtime type errors, fewer manual casts, and clearer API contracts for teams.