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:
| Wildcard | Meaning | Use Case |
|---|---|---|
? | Unknown type | Read-only access to a collection |
? extends T | T or any subtype | Producer (reading elements) |
? super T | T or any supertype | Consumer (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:
- You cannot use
instanceofwith generic types (e.g.,obj instanceof List<String>is illegal). - You cannot create arrays of generic types directly.
- Generic type information is unavailable via reflection at runtime (though workarounds like
TypeTokenexist).
Common Pitfalls to Avoid
- Using raw types (e.g.,
List list = new ArrayList()) defeats the purpose of generics. - Assuming
List<Dog>is a subtype ofList<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.