Understanding Java Generics: Classes, Methods, Wildcards, Bounds, and Type Erasure
This article provides a comprehensive introduction to Java generics, explaining generic classes and methods, the use of wildcards and bounds, the PECS principle, type erasure, and common pitfalls such as generic array creation, bridge methods, and runtime type checks.
Introduction – Generics are a crucial feature of Java and are extensively used in the collection framework. The article starts from zero to explain the design of Java generics, including wildcard handling and the often‑confusing type erasure.
Generic Basics – Generic Class
public class Box {
private String object;
public void set(String object) { this.object = object; }
public String get() { return object; }
}The above class can only store String values. By introducing a type parameter the class becomes reusable:
public class Box
{
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}Now Box<Integer> , Box<Double> , and Box<String> can be created and used interchangeably.
Generic Method
public class Util {
public static
boolean compare(Pair
p1, Pair
p2) {
return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue());
}
}
public class Pair
{
private K key;
private V value;
public Pair(K key, V value) { this.key = key; this.value = value; }
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}Invocation can be explicit:
Pair
p1 = new Pair<>(1, "apple");
Pair
p2 = new Pair<>(2, "pear");
boolean same = Util.
compare(p1, p2);or rely on type inference (Java 7/8):
boolean same = Util.compare(p1, p2);Bounds
Attempting to compare generic elements with the > operator fails because only primitive types support it. The solution is to restrict the type parameter to Comparable :
public static
> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray) {
if (e.compareTo(elem) > 0) ++count;
}
return count;
}Wildcards
Generic types are invariant; Box<Number> is not a supertype of Box<Integer> . Using wildcards restores covariance:
static class CovariantReader
{
T readCovariant(List
list) { return list.get(0); }
}
CovariantReader
fruitReader = new CovariantReader<>();
Fruit f1 = fruitReader.readCovariant(fruit);
Fruit f2 = fruitReader.readCovariant(apples);The PECS principle ( Producer Extends, Consumer Super ) summarizes how to choose wildcards:
Use ? extends T for read‑only (producer) collections.
Use ? super T for write‑only (consumer) collections.
If both read and write are needed, avoid wildcards.
Example of a method that copies from a source to a destination using both forms:
public static
void copy(List
dest, List
src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}Type Erasure
Java generics are implemented via type erasure: the compiler checks types, then replaces type parameters with their bounds (or Object if unbounded). For example:
public class Node
{ private T data; private Node
next; ... }is compiled to:
public class Node { private Object data; private Node next; ... }Consequences include:
Inability to create generic arrays because the runtime cannot distinguish List [] from List [] .
Bridge methods are generated to preserve polymorphism after erasure, which can cause ClassCastException when raw types are used.
Instantiating a type parameter directly ( new E() ) is illegal; reflection or factory patterns are required.
Using instanceof with a generic type is prohibited; a reifiable wildcard type ( ArrayList ) must be used instead.
These issues illustrate why generics provide only compile‑time safety and why developers must understand bounds, wildcards, and erasure to avoid subtle bugs.
Common Pitfalls
Problem 1: Generic array creation is disallowed to prevent runtime type‑confusion.
Problem 2: Bridge methods inserted by the compiler can cause unexpected ClassCastException when raw types are used.
Problem 3: Direct instantiation of a type variable is illegal; use reflection or factories.
Problem 4: instanceof cannot test generic types; use wildcards with reifiable types.
Understanding these concepts helps developers write safer, more expressive Java code.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.