Matheion

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, not int, for Cell<Integer>.
  • Static members cannot refer to the class’s type parameter T as if each instance had its own static—statics belong to the erased class.
  • Repeating verbose generic signatures—extract type aliases via record or nested interface only 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. 1.What is the main benefit of `Box<T>`?

  2. 2.Type erasure means…

  3. 3.`<T extends Number>` on a type parameter means…

  4. 4.A generic method is declared like…

  5. 5.Why not write `ArrayList<int>`?