Generics

Generics let you write classes and methods that work with any type while keeping full type safety. List<String> is a list that can only contain strings — the compiler checks every add and you never need a cast when reading. Understanding generics is essential for using the collections framework and writing reusable code.

Why generics?

JAVA
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Ana");
        names.add("Ben");
        String first = names.get(0);
        System.out.println(first.toUpperCase());
    }
}
Output
ANA

The type argument in angle brackets says what the list holds. Adding a number would be a compile error, and get returns a String directly. Before generics (Java 5), collections held Object, required casts, and type mistakes only showed up at run time. The empty <> on the right — the "diamond" — lets the compiler infer the type.

JAVA
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add(42);
    }
}
Compiler error
Main.java:7: error: incompatible types: int cannot be converted to String

Writing a generic class

JAVA
class Box<T> {
    private T content;

    void put(T item) { content = item; }
    T get() { return content; }
    boolean isEmpty() { return content == null; }
}

public class Main {
    public static void main(String[] args) {
        Box<String> letter = new Box<>();
        letter.put("Hello");
        Box<Integer> number = new Box<>();
        number.put(7);
        System.out.println(letter.get().length() + number.get());
    }
}
Output
12

T is a type parameter — a placeholder filled in when the class is used. By convention, type parameters are single capital letters: T (type), E (element), K/V (key/value).

Several type parameters

JAVA
record Pair<A, B>(A first, B second) {
    <C> Pair<A, C> withSecond(C newSecond) {
        return new Pair<>(first, newSecond);
    }
}

public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> score = new Pair<>("Ana", 91);
        Pair<String, Boolean> passed = score.withSecond(score.second() >= 50);
        System.out.println(score + " " + passed);
    }
}
Output
Pair[first=Ana, second=91] Pair[first=Ana, second=true]

Generic methods

JAVA
import java.util.List;

public class Main {
    static <T> T lastOf(List<T> items) {
        return items.get(items.size() - 1);
    }

    static <T extends Comparable<T>> T largest(List<T> items) {
        T best = items.get(0);
        for (T item : items) {
            if (item.compareTo(best) > 0) best = item;
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(lastOf(List.of("a", "b", "c")));
        System.out.println(largest(List.of(4, 19, 7)));
        System.out.println(largest(List.of("pear", "apple", "zucchini")));
    }
}
Output
c
19
zucchini

A method can declare its own type parameter before the return type. T extends Comparable<T> is a bound: T can be any type that can be compared with itself, which lets the method call compareTo.

Wildcards

JAVA
import java.util.ArrayList;
import java.util.List;

public class Main {
    static double sum(List<? extends Number> numbers) {
        double total = 0;
        for (Number n : numbers) total += n.doubleValue();
        return total;
    }

    static void addDefaults(List<? super Integer> target) {
        target.add(0);
        target.add(100);
    }

    public static void main(String[] args) {
        System.out.println(sum(List.of(1, 2, 3)));
        System.out.println(sum(List.of(1.5, 2.5)));
        List<Number> mixed = new ArrayList<>();
        addDefaults(mixed);
        System.out.println(mixed);
    }
}
Output
6.0
4.0
[0, 100]

A List<Integer> is not a List<Number> (otherwise you could add a Double to a list of integers). Wildcards loosen this: ? extends Number accepts lists of any number type for reading; ? super Integer accepts lists that can receive integers. Mnemonic: "producer extends, consumer super".

Type erasure

Generic type information is checked at compile time and then mostly erased: at run time a List<String> and a List<Integer> are both just List. That's why you can't write new T(), create generic arrays like new T[10], or use primitives as type arguments.

Try it yourself

Write a generic method that counts how many items in a list match a value.

Try it yourself · Main.java
Edit the code, copy it into Main.java and run java Main.java (Java 25+). How to set up Java
Output
3
3