Why do generics exist in Java, and what problem do they actually solve?
Generics were added to move type checking from run time to compile time. Before them, a collection held Object, so putting the wrong type into it compiled perfectly and only blew up later when something tried to use it.
The classic failure is a raw list. You add a String to it, then read it back as an Integer. The compiler cannot help you, because the list has no idea what is inside it. The ClassCastException arrives at run time, possibly far from the code that put the bad value in.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
// Without generics this compiles, then fails at run time.
List raw = new ArrayList();
raw.add("a string");
try {
Integer number = (Integer) raw.get(0);
System.out.println(number);
} catch (ClassCastException e) {
System.out.println("raw list -> ClassCastException");
}
// With generics the mistake cannot compile at all.
List<String> typed = new ArrayList<>();
typed.add("a string");
String text = typed.get(0);
System.out.println("typed list -> " + text);
}
}The output shows the difference in one screen. The raw list accepts anything and fails when the wrong type is pulled back out. The typed list cannot hold the wrong value at all, and get(0) returns a String the compiler knows is a String.
Three benefits follow. Type safety — the compiler rejects the mistake at build time, where it is cheap to fix. No casts — the compiler inserts the cast for you, which removes both the noise and the runtime cost of checking. And better generics — with a raw list, the compiler treats a for-each loop element as Object and refuses to let you call anything on it; with a parameterised list, the element type is known.
The cost is in the language itself: generics are implemented with type erasure so that new generic code can run against old, non-generic libraries. That is what makes generics source-compatible with the pre-2004 world, and it is also what creates the limitations around overloading and primitive type arguments.