Write type-safe, reusable code with Java generics.
Published January 24, 2025
Generics allow you to write classes and methods that work with any type while keeping compile-time type safety.
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
}
Box<String> stringBox = new Box<>("Hello");
Box<Integer> intBox = new Box<>(42);
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
max(3, 7) // → 7 (Integer)
max("apple", "banana") // → "banana" (String)
// T must be a Number or subclass
public static <T extends Number> double sum(List<T> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
// ? extends T — read-only (producer)
void printAll(List<? extends Number> list) {
list.forEach(System.out::println);
}
// ? super T — write-only (consumer)
void addNumbers(List<? super Integer> list) {
list.add(42);
}
PECS — Producer Extends, Consumer Super: use ? extends T when reading, ? super T when writing.
At runtime, generic type information is erased. List<String> and List<Integer> are both just List at runtime. This is why you can't do new T[] or instanceof List<String>.
Know PECS cold. Interviewers love asking: "Why can't you add to a List<? extends Number>?"
Because the compiler doesn't know the actual type at compile time — it could be List<Integer>, List<Double>, etc. Adding an Integer to a List<Double> would be a type error.