Generics Basics
Writing type-safe, reusable classes and methods with generics, and why they replaced raw Object-based collections.
2 min read
You've already been using generics every time you wrote List<String> or Map<String, Integer> — the part in angle brackets is a generic type parameter. Understanding what's happening there lets you write your own reusable, type-safe classes and methods instead of just consuming ones the standard library provides.
The problem generics solve
Before generics (pre-Java 5), collections stored plain Object references, and you had to cast everything back to its real type yourself:
List rawList = new ArrayList(); // no type parameter -- avoid this
rawList.add("hello");
rawList.add(42); // the compiler allows this too -- no type checking at all
String value = (String) rawList.get(1); // compiles, but throws ClassCastException at runtimeThat cast failure only shows up when the code actually runs, potentially far from where the mistake was made. Generics push that check to compile time instead:
List<String> safeList = new ArrayList<>();
safeList.add("hello");
safeList.add(42); // compile error: int cannot be converted to StringWriting a generic class
A class can declare its own type parameter, conventionally a single capital letter like T (Type), E (Element), or K/V (Key/Value):
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}Box<String> stringBox = new Box<>();
stringBox.set("hello");
String value = stringBox.get(); // no cast needed -- the compiler knows it's a String
Box<Integer> intBox = new Box<>();
intBox.set(42);Box<T> is written once but works for any type, with full compile-time type checking for each specific use — that's the core value proposition of generics.
Generic methods
A single method can also declare its own type parameter, independent of the class it's in:
public static <T> T firstElement(List<T> list) {
return list.get(0);
}String first = firstElement(List.of("a", "b", "c")); // T inferred as StringBounded type parameters
Sometimes a generic type needs to guarantee some capability — for example, that it can be compared. A bound restricts what types are allowed:
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}T extends Comparable<T> means "any type T used here must implement Comparable," which is what makes a.compareTo(b) legal to call inside the method.
Generics can feel abstract at first, but the payoff is concrete: fewer casts, fewer ClassCastExceptions, and compile-time errors instead of runtime surprises — which is exactly why virtually the entire Collections Framework is built on them.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.