MA-JAVA0 · Module 17
Generics: type parameters, methods, and bounds
Topics: Type parameters · Generic methods · extends bound · Erasure
Overview
Session 17 — Generics: type parameters, methods, and bounds
You already use ArrayList<String>. This session explains type parameters on your own classes, generic methods whose <T> is declared on the method, bounds such as <T extends Comparable<T>>, and the intuition behind type erasure—why generics mostly disappear at runtime yet still catch mistakes at compile time.
Time: about 95 minutes.
Concepts
1. Generic classes
A type parameter (usually T, E, K, V) lets one class work for many element types safely:
public final class Cell<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
Call sites write Cell<String> or Cell<Integer>; the compiler rejects set(123) on a Cell<String>.
2. Diamond operator
Cell<String> c = new Cell<>();
The empty <> tells the compiler: infer type arguments from the left-hand side—less noise, same safety.
3. Generic methods
Sometimes only a method needs a fresh type variable:
public static <T> T firstOf(T a, T b) {
return a != null ? a : b;
}
Callers may rely on inference: String s = firstOf("x", "y"); The <T> appears before the return type.
4. Bounded type parameters
Require that T supports operations you need:
public static <T extends Comparable<T>> T max(T left, T right) {
return left.compareTo(right) >= 0 ? left : right;
}
Without the bound, compareTo would not compile—T was too general.
5. Multiple bounds
public static <T extends Number & Comparable<T>> int compareAsInt(T a, T b) {
return a.compareTo(b);
}
T must be a subtype of all listed bounds (class first, then interfaces).
6. Erasure (mental model)
The JVM mostly sees Cell with Object or the nearest bound replacing T. The compiler inserts casts and checks so your source stays type-safe. That is why you cannot new T() or do instanceof T—those need reified runtime types.
7. Raw types
Using Cell without a type argument is a raw type—allowed for legacy interop but unsafe. Modern code should always supply type arguments (or use <?> wildcards in APIs—next session).
8. Pitfalls
- Primitive type arguments are illegal: use
Integer, notint, forCell<Integer>. - Static members cannot refer to the class’s type parameter
Tas if each instance had its own static—statics belong to the erased class. - Repeating verbose generic signatures—extract type aliases via
recordor nestedinterfaceonly in advanced style guides; for now, tolerate verbosity.
Examples
Example A — generic pair
public record Pair<A, B>(A first, B second) {}
Usage:
Pair<String, Integer> row = new Pair<>("sku-12", 40);
String sku = row.first();
int qty = row.second();
record components are final; generics on the record propagate to accessors with no casting at call sites.
Example B — generic static utility
import java.util.List;
public class Lists {
private Lists() {}
public static <T> T headOr(List<T> items, T fallback) {
return items.isEmpty() ? fallback : items.get(0);
}
}
Inference ties T to the list’s element type when you call headOr(names, "none") on a List<String>.
Practice check — generics and bounds
Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.
1.What is the main benefit of `Box<T>`?
2.Type erasure means…
3.`<T extends Number>` on a type parameter means…
4.A generic method is declared like…
5.Why not write `ArrayList<int>`?