Level 2 · 20 min
Generics in OOP
Generics provide compile-time type safety and eliminate the need for explicit casts. Java generics use type erasure — generic type information is removed at compile time — which has implications for reflection and certain patterns.
Generics and Type Safety
Without generics: List list = new ArrayList(); list.add(''hello''); String s = (String) list.get(0) — cast can fail at runtime. With generics: List'<'String'>' list = new ArrayList'<''>'() — the compiler guarantees the list only contains Strings. ClassCastException becomes a compile error. Generic methods: '<'T extends Comparable'<'T'>''>' T max(T a, T b) — works for any Comparable type.
Type Erasure
Java generics use erasure — at runtime, List'<'String'>' and List'<'Integer'>' are both just List. The generic type T is erased to Object (or to the bound if bounded). This means: you cannot use T in instanceof checks, you cannot create new T(), and List'<'String'>'.class doesn''t exist — only List.class. Erasure is the reason Java''s generics are sometimes called ''fake generics'' compared to C#.
Wildcards and PECS
PECS: Producer Extends, Consumer Super. List'<'? extends Shape'>' produces Shapes — you can read Shapes from it but not write (type unknown). List'<'? super Circle'>' consumes Circles — you can write Circles into it but reading gives Object. Bounded type parameters: '<'T extends Comparable'<'T'>''>' means T must implement Comparable. Useful for algorithms that need to compare elements. Bloch''s Item 31 (Effective Java) formalizes PECS with a stack example: pushAll(Iterable'<'? extends E'>' src) — the source is a producer of E, so extends applies; popAll(Collection'<'? super E'>' dst) — the destination is a consumer of E, so super applies. Bloch''s rule of thumb: "For maximum flexibility, use wildcard types on input parameters that represent producers or consumers. Do not use wildcard types as return types" — doing so forces clients to use wildcards in their own code. Collections.sort(List'<'T'>', Comparator'<'? super T'>') is the canonical example from the JDK: a Comparator'<'Object'>' can sort a List'<'String'>' because Object is a supertype of String.
Code example
// Generic stack with type safety
class Stack<T> {
private final Deque<T> storage = new ArrayDeque<'>();
void push(T item) { storage.push(item); }
T pop() { return storage.pop(); }
T peek() { return storage.peek(); }
}
// Generic method: works for any Comparable
<T extends Comparable<T>'> T max(List<T> list) {
return list.stream().max(Comparator.naturalOrder()).orElseThrow();
}
// PECS: copy from producer to consumer
<T> void copy(List<? extends T> source, List<? super T> destination) {
for (T item : source) destination.add(item);
}
// copy(List<Integer>, List<Number>) — Number is super of Integer