Bounded Generics with extends
Bounded type parameters and wildcards (`<T extends Number>`, `<? extends Number>`, `<? super Integer>`) are how Java balances type safety with API flexibility. This snippet shows an upper-bounded type parameter for arithmetic, the producer-extends-consumer-super (PECS) rule with wildcards, and a comparator-driven generic max function. Get these right and your collection APIs will accept everything the caller reasonably wants to pass.
745 views
9
import java.util.*;
import java.util.Comparator;
public class Main {
// T is restricted to Number subclasses, so .doubleValue() is safe.
static <T extends Number> double sum(List<T> list) {
double total = 0;
for (T n : list) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> dbls = Arrays.asList(1.5, 2.5);
System.out.println("sum ints = " + sum(ints));
System.out.println("sum dbls = " + sum(dbls));
// sum(Arrays.asList("a")) would not compile: String is not a Number.
}
}Declaring <T extends Number> constrains the type parameter so the method body can call any Number member like doubleValue(). Without the bound, T would be Object and you would not be able to do arithmetic on it. The single bound is the common case; multiple bounds are written <T extends Number & Comparable<T>> (only the first may be a class). Use this when the method genuinely needs to call a method on T; if it only stores or returns the value, a wildcard is usually a better fit (next accordion).
import java.util.*;
public class Main {
// src is a producer of Number-or-subclass: read with extends.
// dst is a consumer of Integer-or-supertype: write with super.
static void copyInts(List<? extends Integer> src, List<? super Integer> dst) {
for (Integer n : src) dst.add(n);
}
public static void main(String[] args) {
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> dst = new ArrayList<>();
copyInts(src, dst);
System.out.println(dst); // [1, 2, 3]
// Without ? super Integer, dst could not accept Integers when typed as List<Number>
// generically without an explicit cast. PECS captures this directly.
}
}PECS stands for "Producer Extends, Consumer Super". A list you READ from (a producer of T) should be List<? extends T>, and a list you WRITE to (a consumer of T) should be List<? super T>. The asymmetry exists because Java generics are invariant by default: List<Integer> is NOT a subtype of List<Number>. Wildcards relax the rule for one direction at a time. Apply PECS to any method signature that takes two collections of related types; the extra typing pays back in caller flexibility.
import java.util.*;
public class Main {
static <T> T maxBy(List<? extends T> items, Comparator<? super T> cmp) {
Iterator<? extends T> it = items.iterator();
T best = it.next();
while (it.hasNext()) {
T cur = it.next();
if (cmp.compare(cur, best) > 0) best = cur;
}
return best;
}
public static void main(String[] args) {
List<String> words = Arrays.asList("alpha", "beta", "gamma", "d");
System.out.println("longest=" + maxBy(words, Comparator.comparingInt(String::length)));
List<Integer> nums = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
System.out.println("max=" + maxBy(nums, Comparator.naturalOrder()));
}
}<T> introduces a generic parameter; List<? extends T> lets callers pass List<Integer> when T = Number; Comparator<? super T> lets a comparator declared for a supertype be reused. This is exactly how Collections.max is signed in the standard library. The pattern keeps the API maximally permissive without losing type safety: a Comparator<Number> can sort List<Integer> because every Integer IS a Number. When in doubt, copy the standard library's signatures: they have been refined over decades.
