Generics

Why Generics

Generics move type errors from runtime to compile time: a List<String> can only ever hold strings, and reading from it needs no cast. Raw types exist only for backward compatibility — never write them.
  • Pre-generics collections held Object — every read needed a cast that could fail at runtime
  • Type parameters document intent and are enforced by the compiler
  • The diamond <> infers type arguments: new ArrayList<>()
  • Raw types (List without <...>) silently opt out of type checking (EJ 26)
  • List<?> is the safe "list of unknown" — raw List is not
Before and after
// Pre-Java-5: trust, then ClassCastException at a distance
List files = new ArrayList();
files.add("readme.txt");
files.add(42);                              // compiles!
String f = (String) files.get(1);           // BOOM — at runtime, far from the bug

// Generics: the compiler rejects the bad add
List<String> names = new ArrayList<>();
names.add("readme.txt");
// names.add(42);                           // compile error — bug caught at source
String first = names.get(0);                // no cast

Generics also power the fluent APIs you use daily: Stream<T>, Optional<T>, CompletableFuture<T>, Map<K,V>. Understanding variance (Wildcards Pecs) and Type Erasure explains both their signatures and their few sharp edges.

Sources
  • Core Java, Volume I: Fundamentals (13th ed.)Ch. 8.1 — Why Generic Programming?
  • Effective Java (3rd ed.)Items 26, 27