EMZETT.
Login

Generics

In short: Type parameters in angle brackets (<T>), which make a class or method reusable for arbitrary types without giving up type safety.

In more detail: Without generics, a collection would have to work with the general type Object and manually cast values back when retrieving them — error-prone, since type errors are only caught at runtime. With List<String>, by contrast, the compiler already enforces at compile time that only String objects go in.

class Box<T> {
    private T content;
    void set(T content) { this.content = content; }
    T get() { return content; }
}

In Depth

class Box<T> {
    private T content;
    void set(T content) { this.content = content; }
    T get() { return content; }
}
 
Box<String> textBox = new Box<>();
textBox.set("Hello");
String value = textBox.get(); // no cast needed - compiler knows the type
 
// Generic method with its own type parameter
static <T> T firstElement(List<T> list) {
    return list.get(0);
}
 
// Bounded type parameter - T must be Number or a subclass
static <T extends Number> double sum(List<T> numbers) {
    double sum = 0;
    for (T number : numbers) sum += number.doubleValue();
    return sum;
}

Generics exist ONLY at compile time (“type erasure”) — at runtime, the JVM actually no longer knows that a List<String> specifically contains strings, it just sees a completely normal List. This explains why, for example, you can’t directly write new T[10] (creating an array of a generic type) in Java, and why instanceof List<String> doesn’t work (only instanceof List is allowed). The advantage despite this limitation: the compiler catches type errors already while the code is being written, before the program even runs — without generics, such errors would often only surface as a ClassCastException in the middle of execution. <T extends Number> (bounded type parameter) restricts WHICH types can be used as T, and at the same time allows calling methods of Number (like doubleValue()) directly on T values.

Wildcards: ? extends and ? super

Besides concrete type parameters (<T>), Java also supports wildcards for methods that need to read OR write a collection without knowing the exact type:

// ? extends Number - can read from ANY list of a Number subclass (Integer, Double, ...)
static double sumOf(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) sum += n.doubleValue();
    return sum;
}
 
sumOf(List.of(1, 2, 3));       // List<Integer> works
sumOf(List.of(1.5, 2.5));      // List<Double> works too

The rule of thumb for this is “PECS” (Producer Extends, Consumer Super): if a method only READS from the collection (produces values for the caller), use ? extends T; if it only WRITES into the collection (consumes values from the caller), use ? super T.

Why no arrays of generic types

A common compiler error when experimenting with generics is trying to create an array of a generic type:

class Container<T> {
    // T[] elements = new T[10]; // compiler error: "generic array creation"
    Object[] elements = new Object[10]; // workaround: create as Object[], then cast internally
}

This is directly due to type erasure: arrays in Java “know” at runtime which type they contain (for type checks when inserting), but generics no longer do — this incompatibility is prohibited by the compiler from the outset, rather than allowing a potentially faulty array at runtime.

See also: Wrapper Classes, Collections, List