Command Palette

Search for a command to run...

EN·ES

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.

Key Takeaways

  • Generics provide compile-time type safety — prefer them over Object + cast.
  • Type erasure: List'<'String'>' and List'<'Integer'>' are the same class at runtime — cannot use instanceof with generics.
  • PECS: extends for reading (producer), super for writing (consumer).

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