Generics
In short: Type parameters in angle brackets (<T>), which make a class or method reusable for arbitrary types without giving up type safety.
In more detail: Without generics, a collection would have to work with the general type Object and manually cast values back when retrieving them — error-prone, since type errors are only caught at runtime. With List<String>, by contrast, the compiler already enforces at compile time that only String objects go in.
class Box<T> {
private T content;
void set(T content) { this.content = content; }
T get() { return content; }
}In Depth
class Box<T> {
private T content;
void set(T content) { this.content = content; }
T get() { return content; }
}
Box<String> textBox = new Box<>();
textBox.set("Hello");
String value = textBox.get(); // no cast needed - compiler knows the type
// Generic method with its own type parameter
static <T> T firstElement(List<T> list) {
return list.get(0);
}
// Bounded type parameter - T must be Number or a subclass
static <T extends Number> double sum(List<T> numbers) {
double sum = 0;
for (T number : numbers) sum += number.doubleValue();
return sum;
}Generics exist ONLY at compile time (“type erasure”) — at runtime, the JVM actually no longer knows that a List<String> specifically contains strings, it just sees a completely normal List. This explains why, for example, you can’t directly write new T[10] (creating an array of a generic type) in Java, and why instanceof List<String> doesn’t work (only instanceof List is allowed). The advantage despite this limitation: the compiler catches type errors already while the code is being written, before the program even runs — without generics, such errors would often only surface as a ClassCastException in the middle of execution. <T extends Number> (bounded type parameter) restricts WHICH types can be used as T, and at the same time allows calling methods of Number (like doubleValue()) directly on T values.
Wildcards: ? extends and ? super
Besides concrete type parameters (<T>), Java also supports wildcards for methods that need to read OR write a collection without knowing the exact type:
// ? extends Number - can read from ANY list of a Number subclass (Integer, Double, ...)
static double sumOf(List<? extends Number> list) {
double sum = 0;
for (Number n : list) sum += n.doubleValue();
return sum;
}
sumOf(List.of(1, 2, 3)); // List<Integer> works
sumOf(List.of(1.5, 2.5)); // List<Double> works tooThe rule of thumb for this is “PECS” (Producer Extends, Consumer Super): if a method only READS from the collection (produces values for the caller), use ? extends T; if it only WRITES into the collection (consumes values from the caller), use ? super T.
Why no arrays of generic types
A common compiler error when experimenting with generics is trying to create an array of a generic type:
class Container<T> {
// T[] elements = new T[10]; // compiler error: "generic array creation"
Object[] elements = new Object[10]; // workaround: create as Object[], then cast internally
}This is directly due to type erasure: arrays in Java “know” at runtime which type they contain (for type checks when inserting), but generics no longer do — this incompatibility is prohibited by the compiler from the outset, rather than allowing a potentially faulty array at runtime.
See also: Wrapper Classes, Collections, List