Bounded type parameters: extends and multiple bounds
Restricting the types a parameter accepts with upper bounds and & for multiple constraints.
Restricting what T can be
An upper bound (T extends SomeType) constrains the allowed types and unlocks the methods of the bound:
// T must implement Comparable so we can call compareTo
public static <T extends Comparable<T>> T max(List<T> xs) {
T result = xs.get(0);
for (T item : xs) {
if (item.compareTo(result) > 0) result = item;
}
return result;
}
max(List.of(3, 1, 4, 1, 5)); // Integer implements Comparable<Integer>
max(List.of("fig", "apple")); // String implements Comparable<String>
Without the bound the compiler would not allow item.compareTo(...) — it only knows T is Object.
extends means both extends and implements in a type-parameter context: <T extends Number> works for Integer, Double, BigDecimal, etc.
Multiple bounds use &. A class bound must come first, followed by interface bounds:
public <T extends Number & Comparable<T>> T clamp(T val, T lo, T hi) {
if (val.compareTo(lo) < 0) return lo;
if (val.compareTo(hi) > 0) return hi;
return val;
}
The & bound list means T must satisfy all constraints simultaneously. You can have at most one class in a bound (since Java has single class inheritance) but as many interfaces as you need.