Why generics: type safety and the diamond operator
How generics replaced raw types — type-checked at compile time, zero runtime cost.
The problem generics solve
Before Java 5, collections held Object references. You had to cast on every read, and a wrong cast compiles silently but blows up at runtime:
// Raw type — pre-generics style (avoid this)
List names = new ArrayList();
names.add("Alice");
names.add(42); // compiles — no type check!
String s = (String) names.get(1); // ClassCastException at runtime
Generics bind a type parameter to a collection so the compiler enforces the constraint:
List<String> names = new ArrayList<String>();
names.add("Alice");
// names.add(42); // compile error — int is not String
String s = names.get(0); // no cast needed
The diamond operator <> (Java 7+) lets the compiler infer the type argument from the declared type, so you don't repeat yourself:
List<String> names = new ArrayList<>(); // diamond: infers String
Map<String, Integer> counts = new HashMap<>(); // infers both params
Generics are compile-time only: the bytecode has no type arguments (this is called *type erasure*, covered at the end of this module). At runtime both List<String> and List<Integer> are just List.