Java Bounded Type Parameters
By default, a type parameter <T> can be any reference type. Bounded type parameters restrict T to a specific type or its subtypes — letting you call methods on T that you know exist.
Upper Bounded: <T extends Type>
Restrict T to Type or any subclass/implementor:
// T must be a Number or subclass (Integer, Double, Long, ...)
public static <T extends Number> double sum(List<T> numbers) {
double total = 0;
for (T n : numbers) {
total += n.doubleValue(); // ✅ Can call Number methods on T
}
return total;
}
sum(List.of(1, 2, 3)); // T = Integer → 6.0
sum(List.of(1.5, 2.5)); // T = Double → 4.0
// sum(List.of("a", "b")); // ❌ Compile error — String is not a Number
Without the bound, you could only call Object methods on T.
Why Bounds Are Needed
// Without bound — can't call doubleValue()
public static <T> double sum(List<T> numbers) {
for (T n : numbers) {
n.doubleValue(); // ❌ Compile error — T could be anything
}
}
// With bound — compiler knows T is at least a Number
public static <T extends Number> double sum(List<T> numbers) {
for (T n : numbers) {
n.doubleValue(); // ✅ Number has doubleValue()
}
}
Multiple Bounds
T can extend a class and implement interfaces:
// T must extend Number AND implement Comparable
public static <T extends Number & Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
max(10, 20); // ✅ Integer extends Number, implements Comparable
max(1.5, 2.5); // ✅ Double extends Number, implements Comparable
Rules for Multiple Bounds
<T extends ClassA & InterfaceB & InterfaceC>
- At most one class — and it must come first.
- Multiple interfaces are allowed.
- Separated by
&.
// ✅ Valid
<T extends Number & Comparable<T> & Serializable>
// ❌ Invalid — two classes
<T extends Number & String>
// ❌ Invalid — class must come first
<T extends Comparable<T> & Number>
Bounded Types on Classes
public class NumericBox<T extends Number> {
private T value;
public NumericBox(T value) {
this.value = value;
}
public double doubleValue() {
return value.doubleValue();
}
public int intValue() {
return value.intValue();
}
}
NumericBox<Integer> intBox = new NumericBox<>(42);
NumericBox<Double> dblBox = new NumericBox<>(3.14);
// NumericBox<String> strBox = new NumericBox<>("hi"); // ❌ Compile error
Recursive Type Bounds
A type parameter that refers to itself — commonly used for comparable types:
// T must be comparable to itself
public static <T extends Comparable<T>> T findMax(List<T> list) {
if (list.isEmpty()) throw new NoSuchElementException();
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
findMax(List.of(3, 1, 4, 1, 5)); // 5
findMax(List.of("banana", "apple")); // "banana"
The “Curiously Recurring Template Pattern”
public abstract class BaseEntity<T extends BaseEntity<T>> {
private Long id;
@SuppressWarnings("unchecked")
public T withId(Long id) {
this.id = id;
return (T) this; // returns the subclass type
}
}
public class User extends BaseEntity<User> {
private String name;
public User withName(String name) {
this.name = name;
return this;
}
}
// Fluent API — returns the correct type at each step
User user = new User().withId(1L).withName("Alice");
Practical Example: Generic Sort with Bounds
public class SortUtil {
// Only accepts lists of Comparable elements
public static <T extends Comparable<T>> void bubbleSort(List<T> list) {
int n = list.size();
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (list.get(j).compareTo(list.get(j + 1)) > 0) {
T temp = list.get(j);
list.set(j, list.get(j + 1));
list.set(j + 1, temp);
}
}
}
}
}
List<Integer> nums = new ArrayList<>(List.of(5, 3, 8, 1));
SortUtil.bubbleSort(nums); // [1, 3, 5, 8]
Why No <T super Type>?
You cannot use super with type parameter declarations:
// ❌ Not valid syntax
public class Box<T super Number> { }
// ❌ Not valid syntax
public static <T super Integer> void process(T item) { }
super is only used with wildcards (? super T), which we cover in the next chapter.
Why? A lower bound on a type parameter wouldn’t provide useful compile-time guarantees — you couldn’t call any specific methods on T.
Key Takeaways
<T extends Type>restrictsTtoTypeand its subtypes.- Bounds let you call methods on
Tthat are defined in the bound type. - Multiple bounds: one class (first) + multiple interfaces, joined by
&. - Recursive bounds (
T extends Comparable<T>) are common for self-referential types. superis not allowed on type parameter declarations — only on wildcards.
What’s Next?
Next: Wildcards in Generics — ?, ? extends T, ? super T, and the PECS principle.