Skip to content
Part IA Michaelmas Term

Why Generics?

The Pre-Generics World

Before Java 5 (2004), collections stored Object, requiring casts on retrieval and deferring type errors to runtime:

List list = new ArrayList();
list.add("hello");
list.add(42);                       // perfectly legal — List<Object>

String s = (String) list.get(0);    // works, but requires a cast
String t = (String) list.get(1);    // compiles, but ClassCastException at RUNTIME

The compiler had no way to know that list was intended to hold String objects. Every retrieval required a cast, and every cast was a potential ClassCastException waiting to happen — at runtime, when it is too late to fix the code before shipping.

This was not merely inconvenient. It moved type errors from compile time (when the developer is at their desk) to runtime (when the user is staring at a crash). In large codebases, tracking down which of a thousand add() calls inserted the wrong type was a nightmare.

The Generic Solution

Generics allow a class or method to be parameterised by a type:

List<String> safeList = new ArrayList<>();
safeList.add("hello");
safeList.add(42);  // COMPILE ERROR — cannot add Integer to List<String>

String s = safeList.get(0);  // no cast needed — compiler knows it's String

The compiler now enforces that only String objects enter this list. The cast on retrieval is generated by the compiler rather than written by the programmer — invisible and guaranteed correct.

Four Benefits

1. Compile-Time Type Checking

Type errors are caught before the program ever runs. If the code compiles without warnings, there will be no ClassCastException from the generic code (assuming no raw-type contamination).

2. Elimination of Explicit Casts

List<String> names = new ArrayList<>();
names.add("Alice");
String first = names.get(0);  // no (String) cast — the compiler inserts it silently

The code is more concise and less error-prone. There is no chance of omitting or mistyping a cast.

3. Cleaner, More Readable Code

The type parameter documents intent:

Map<String, List<Integer>> studentScores = new HashMap<>();

A reader immediately knows this maps student names to lists of integer scores. Without generics, this would be Map — and the reader would need to trace through the code to discover what types the keys and values actually are.

4. Enables Generic Algorithms

A single method can operate on many types while maintaining type safety:

public static <T> boolean contains(T[] array, T target) {
    for (T element : array) {
        if (element.equals(target)) return true;
    }
    return false;
}

This works for String[], Integer[], Person[], etc. — compiled once, safe for all. Without generics, you would write one version per type (duplication), write one version using Object[] (losing type safety), or lean on runtime reflection (slow, fragile).

The Primary Motivator: Collections

While generics apply broadly, collections are the primary motivator. The java.util collections framework was retrofitted with generics in Java 5. Every collection type (List, Set, Map, Queue, etc.) gained type parameters. Today, using a raw (non-generic) collection type generates a compiler warning and should be avoided except when interacting with legacy pre-Java-5 code.

What Generics Are Not

Generics in Java are a compile-time mechanism. At runtime, the type parameters are erased (see type erasure). This is fundamentally different from C++ templates, which generate specialised code for each type argument at compile time. Java’s design choice prioritises backward compatibility with pre-generics bytecode.