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 runtimeWith generics
Java
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(42); ← compile error, caught immediately
String s = list.get(0); // no cast neededA 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 allowedYou also cannot use primitives as type arguments — List<int> is
invalid, use List<Integer>.