What Are Java Generics?

Introduced in Java 5, generics allow you to write classes, interfaces, and methods that operate on a parameterized type. Instead of writing separate implementations for List<String> and List<Integer>, you write a single generic implementation that the compiler tailors for you — with full type safety checked at compile time.

Why Generics Matter

  • Type safety: Catch type mismatches at compile time, not at runtime.
  • Elimination of casts: No more verbose and error-prone (String) list.get(0) casts.
  • Reusability: Write one algorithm that works across multiple types.
  • Better tooling: IDEs can provide smarter autocompletion and refactoring support.

Generic Classes and Methods

A generic class is declared with a type parameter in angle brackets:

public class Box<T> {
    private T value;

    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

You can also create generic methods independently of the class they belong to:

public <T extends Comparable<T>> T findMax(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

Wildcards: ?, extends, and super

Wildcards give you flexibility when the exact type isn't known or needs to vary:

WildcardMeaningUse Case
?Unknown typeRead-only access to a collection
? extends TT or any subtypeProducer (reading elements)
? super TT or any supertypeConsumer (writing elements)

The PECS principle (Producer Extends, Consumer Super) is your rule of thumb when choosing between bounded wildcards.

Type Erasure: The Hidden Mechanic

At runtime, Java erases generic type information — all List<String> and List<Integer> instances become plain List objects. This is called type erasure, and it means:

  1. You cannot use instanceof with generic types (e.g., obj instanceof List<String> is illegal).
  2. You cannot create arrays of generic types directly.
  3. Generic type information is unavailable via reflection at runtime (though workarounds like TypeToken exist).

Common Pitfalls to Avoid

  • Using raw types (e.g., List list = new ArrayList()) defeats the purpose of generics.
  • Assuming List<Dog> is a subtype of List<Animal> — it is not (covariance does not apply).
  • Overcomplicating wildcard hierarchies; prefer simple bounded types when possible.

Key Takeaways

Generics are foundational to writing robust, reusable Java code. Master the basics first — generic classes and methods — then gradually explore wildcards and bounded type parameters. Understanding type erasure will save you from mysterious runtime surprises and deepen your appreciation of how the JVM works.