Generics give you compile-time type safety without casting. The tricky parts — wildcards and erasure — become clear once you learn the guiding principle: PECS.
Producer extends, consumer super
If a structure produces values you read, use extends. If it consumes values
you write, use super. This is the PECS rule.
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}Bounded type parameters
Constrain a type parameter so you can call methods on it safely.
static <T extends Comparable<T>> T max(List<T> items) {
return items.stream().max(Comparator.naturalOrder()).orElseThrow();
}Takeaways
- Type information is erased at runtime — you cannot do
new T()orinstanceof T. - Use bounded wildcards to make APIs accept a wider range of arguments.
- Prefer generic methods over raw types to keep casts out of your code.
Because of erasure, you cannot create an array of a generic type directly;
create Object[] and cast, or use a List instead.