Java Generics

Generics let a class or method work with any type while keeping full compile-time type safety.

Without generics

Java
List raw = new ArrayList();
raw.add("hello");
raw.add(42);                       // allowed — nothing checks
String s = (String) raw.get(1);    // ClassCastException at runtime

With generics

Java
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(42);          ← compile error, caught immediately
String s = list.get(0);    // no cast needed

A generic class

Java
public class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get()           { return value; }
}

Box<String> b1 = new Box<>();
b1.set("hello");
String s = b1.get();

Box<Integer> b2 = new Box<>();
b2.set(42);

T is a placeholder filled in at use. Conventional names: T type, E element, K key, V value, R result.

A generic method

Java
public static <T> void printAll(List<T> items) {
    for (T item : items) {
        System.out.println(item);
    }
}

Bounded types

Restrict what T can be, which also unlocks that type's methods:

Java
public static <T extends Number> double sum(List<T> nums) {
    double total = 0;
    for (T n : nums) {
        total += n.doubleValue();   // available because T is a Number
    }
    return total;
}

Wildcards

Java
// read-only: any list of Number or a subclass
public static double total(List<? extends Number> nums) { }

// write-friendly: any list that can accept Integers
public static void addNumbers(List<? super Integer> list) {
    list.add(1);
}

The mnemonic is PECS — Producer extends, Consumer super. If the parameter produces values you read, use extends; if it consumes values you write, use super.

Type erasure

Generics exist only at compile time; the JVM sees plain List. Two consequences:

Java
// List<String> and List<Integer> are the same class at runtime
System.out.println(new ArrayList<String>().getClass() ==
                   new ArrayList<Integer>().getClass());   // true

// you cannot create an array of a generic type
// T[] arr = new T[10];   ← not allowed

You also cannot use primitives as type arguments — List<int> is invalid, use List<Integer>.