JavaGenerics

Generics

Generics let you parameterize a class, interface, or method with a type, so the compiler can enforce type safety at compile time instead of you discovering a type mismatch at runtime. They were added in Java 5, and today they are everywhere — every collection in the Java Collections Framework is generic.

Life before generics

Before Java 5, collections like ArrayList stored plain Object references. That meant you could put anything into a list, and you had to manually cast every element back to its real type when you read it out. The compiler had no way to catch a mistake — it only showed up as a ClassCastException at runtime, possibly far away from the line that actually caused it.

Before generics — unsafe and verbose

Java
List names = new ArrayList();   // raw type, stores Object
names.add("Alice");
names.add(42);                  // compiler allows this — no type checking!

String first = (String) names.get(0);  // manual cast required
String second = (String) names.get(1); // compiles fine...
                                        // ...but throws ClassCastException at runtime
The type-safe version

With generics, you tell the compiler up front what type a collection (or class) is meant to hold, using angle brackets: List<String> means “a List of String objects.” The compiler then rejects the wrong type immediately, at compile time, and no cast is needed when reading elements back out.

After generics — safe and cast-free

Java
List<String> names = new ArrayList<>();
names.add("Alice");
names.add(42);        // compile-time error: incompatible types

String first = names.get(0);   // no cast needed — the compiler knows it's a String

This is the core benefit of generics: catching type errors at compile time instead of runtime, and eliminating boilerplate casts for the caller.

Generic classes

You are not limited to using generics on built-in collections — you can write your own generic classes. A type parameter (conventionally a single uppercase letter like T, E, K, or V) acts as a placeholder for whatever type the caller supplies when they use the class.

A simple generic container class

Java
public class Box<T> {
    private T content;

    public void set(T content) {
        this.content = content;
    }

    public T get() {
        return content;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello");
        String value = stringBox.get();   // no cast needed

        Box<Integer> intBox = new Box<>();
        intBox.set(42);
        // intBox.set("oops");            // compile-time error
    }
}

The same Box class works for String, Integer, or any other type — one class definition, many type-safe instantiations. This is the same idea ArrayList<T> uses internally.

Type erasure
Generic type information only exists at **compile time**. Once the code compiles, the Java compiler *erases* the type parameters and replaces them with their bound (usually `Object`), inserting the necessary casts for you behind the scenes. This process is called **type erasure**, and it exists so that generic code stays compatible with older, pre-generics bytecode and libraries. A practical consequence: at runtime there is no `T` left to inspect, so you cannot write `new T()`, `instanceof T`, or `T.class` inside a generic class — the compiler simply does not have that information anymore by the time the code runs.
Warning
Because of type erasure, `List<String>` and `List<Integer>` are the *exact same class* at runtime — both are just `List`. Two overloaded methods that differ only by generic type argument (like `process(List<String>)` and `process(List<Integer>)`) will not compile, because after erasure they have identical signatures.
  • Generics add compile-time type checking to classes, interfaces, and methods, eliminating manual casts and runtime ClassCastExceptions.

  • Angle-bracket syntax like List<String> or Box<T> declares the type a generic is parameterized over.

  • You can define your own generic classes with a type parameter such as <T> on the class declaration.

  • Type erasure removes generic type information at runtime, which is why new T() and instanceof T are not allowed.