Introduction
Generics, introduced in Java 5, let you parameterize classes, interfaces, and methods with types. Before generics, collections held Object references and required explicit casts. Generics give you compile-time type safety and eliminate casts, making code safer and more readable.
Without vs With Generics
// Pre-generics
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // cast required
// With generics
List<String> list2 = new ArrayList<>();
list2.add("hello");
String s2 = list2.get(0); // no castGeneric Class
public class Box<T> {
private T value;
public void set(T v) { value = v; }
public T get() { return value; }
}
Box<Integer> b1 = new Box<>();
b1.set(42);
Box<String> b2 = new Box<>();
b2.set("Hello");The T is a type parameter. Convention names: T (type), E (element), K (key), V (value), N (number).
General Form of a Generic Class
public class Pair<K, V> {
private K key;
private V value;
public Pair(K k, V v) { key = k; value = v; }
public K getKey() { return key; }
public V getValue() { return value; }
}Generic Methods
public static <T> T firstOf(List<T> list) {
return list.isEmpty() ? null : list.get(0);
}
String s = firstOf(Arrays.asList("a","b"));
Integer i = firstOf(Arrays.asList(1,2,3));The type parameter <T> appears before the return type. The compiler infers T from the argument.
Bounded Type Parameters
public static <T extends Number> double sum(List<T> nums) {
double total = 0;
for (T n : nums) total += n.doubleValue();
return total;
}<T extends Number> means T must be Number or a subclass. You can compose bounds: <T extends Number & Comparable<T>>.
Wildcards
List<?>— list of unknown type (read-only)List<? extends Number>— upper bound; any Number subtype (producer)List<? super Integer>— lower bound; Integer or any supertype (consumer)
The PECS rule: Producer Extends, Consumer Super.
Generic Interfaces
public interface Container<T> {
void add(T item);
T get(int i);
}
public class StringList implements Container<String> {
private List<String> items = new ArrayList<>();
public void add(String s) { items.add(s); }
public String get(int i) { return items.get(i); }
}Generic Constructors
A generic class constructor can itself have its own type parameter independent of the class:
class Holder<T> {
private T value;
public <U extends T> Holder(U u) { value = u; }
}Type Erasure
Java implements generics using type erasure: the compiler verifies types at compile time, then removes parameter types at runtime. After compilation, List<String> and List<Integer> are both just List. Consequences: you cannot write new T(), T.class, or instanceof T.
Polymorphism in Generics
List<Integer> is not a subtype of List<Number> even though Integer is a subtype of Number. Use wildcards (List<? extends Number>) to achieve polymorphic behaviour safely.
Summary
Generics provide compile-time type safety and eliminate casts. Parameterize classes and methods with type variables, apply bounded types for constraints, and use wildcards for flexibility. Remember type erasure — generics live only at compile time.
Important Questions
- What are generics? Why were they added to Java 5?
- Write a generic class
Pair<K,V>. - What are bounded type parameters? Give an example.
- Explain wildcards:
?,? extends,? super. - State and explain the PECS rule.
- Write a generic method that returns the maximum of a list.
- What is type erasure? List its consequences.
- Why is
List<Integer>not a subtype ofList<Number>?