Java Generics
Java generics let you write classes and methods that work with any type while keeping full type safety. They are why List<String> only accepts strings and why you never need a cast when you read from it.
Why Generics?
Without generics, a list holds plain Object values and you must cast every item, risking a ClassCastException at runtime. With generics, the compiler catches type mistakes before the program runs.
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Zara");
// names.add(42); // compile error: int cannot be converted to String
String first = names.get(0); // no cast needed
System.out.println(first.toUpperCase());
}
}ZARA
Writing a Generic Class
Declare a type parameter in angle brackets after the class name. T is a placeholder that the user fills in.
class Box<T> {
private final T value;
Box(T value) { this.value = value; }
T get() { return value; }
boolean isEmpty() { return value == null; }
}
class Pair<K, V> {
final K key; final V value;
Pair(K key, V value) { this.key = key; this.value = value; }
@Override public String toString() { return key + "=" + value; }
}
public class Main {
public static void main(String[] args) {
Box<Integer> numberBox = new Box<>(7);
Box<String> textBox = new Box<>("hi");
System.out.println(numberBox.get() * 6);
System.out.println(textBox.get().length());
System.out.println(new Pair<>("age", 30));
}
}42 2 age=30
Generic Methods and Bounded Types
A method can declare its own type parameter before the return type. A bound like <T extends Comparable<T>> restricts T to types that can be compared, so you can call compareTo on them.
import java.util.List;
public class Main {
static <T> T firstOrDefault(List<T> list, T fallback) {
return list.isEmpty() ? fallback : list.get(0);
}
static <T extends Comparable<T>> T largest(List<T> items) {
T best = items.get(0);
for (T item : items) {
if (item.compareTo(best) > 0) best = item;
}
return best;
}
public static void main(String[] args) {
System.out.println(firstOrDefault(List.of(), "none"));
System.out.println(largest(List.of(4, 11, 7)));
System.out.println(largest(List.of("pear", "apple", "plum")));
}
}none 11 plum
Wildcards
A wildcard ? means "some unknown type". List<? extends Number> accepts a list of Integer, Double or any Number subtype, which is ideal when you only read from it.
import java.util.List;
public class Main {
static double sum(List<? extends Number> numbers) {
double total = 0;
for (Number n : numbers) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.5, 2.5)));
}
}6.0 4.0
Naming Conventions
- T: type, E: element (collections), K and V: key and value, R: return type.
- Remember "PECS": Producer Extends, Consumer Super. Use ? extends T to read, ? super T to write.
- Generic type information is erased at runtime, so you cannot write new T() or instanceof List<String>.
Frequently asked questions
What does <T> mean in Java?
It declares a type parameter named T, a placeholder for a real type supplied when the class or method is used, such as Box<String>.
What is the difference between <? extends T> and <? super T>?
? extends T accepts T or its subtypes and is safe for reading. ? super T accepts T or its supertypes and is safe for adding T values.
What is type erasure in Java?
The compiler checks generic types and then removes them, so at runtime a List<String> is just a List. This keeps compatibility with older Java code.