🧪 Generics · Intermediate

Bounded type parameters in Java

<T extends Number>, multiple bounds with &.

🧩 The mysteryYou want a sum method for Integers, Doubles AND Longs, but x.doubleValue() won't compile on a plain T. How do you tell Java "T is some kind of Number"?

A plain T knows nothing

Inside <T>, the compiler only knows T is some Object. So only Object's methods are available: no doubleValue(), no compareTo().

static <T> double sum(List<T> xs) {
    double t = 0;
    for (T x : xs) t += x.doubleValue(); // ✗
    return t;
}

Add a bound

**<T extends Number> means T must be Number or a subclass. That restriction unlocks the bound's methods** on T: every T is guaranteed to have doubleValue().

static <T extends Number> double sum(
        List<T> xs) {
    double t = 0;
    for (T x : xs) t += x.doubleValue(); // ✓
    return t;
}
🔮 Predict it

Your turn

What does this print?

static <T extends Number> double sum(
        List<T> xs) {
    double t = 0;
    for (T x : xs) t += x.doubleValue();
    return t;
}
void main() {
    IO.println(sum(List.of(1, 0.5, 2L)));
}
  1. 3
  2. 3.5
  3. Compile error
Show the answer

Integer, Double and Long are all Numbers, so the call compiles. Each becomes a double: 1.0 + 0.5 + 2.0 = 3.5.

🔮 Predict it

Strings at the door

What happens?

static <T extends Number> double sum(
        List<T> xs) {
    return 0;
}
void main() {
    System.out.println(sum(List.of("1", "2")));
}
  1. Prints 0.0
  2. Compile error
  3. Throws ClassCastException
Show the answer

String is not a subtype of Number, so it doesn't satisfy the bound. The call is rejected at compile time, before any code runs.

extends means "is a subtype of"

In a bound, **extends covers both classes and interfaces**. There's no implements in type parameters: a bound only cares that T is a subtype, not how it got there.

<T extends Number>          // a class
<T extends Runnable>        // an interface
<T extends Comparable<T>>   // also fine
⚠️ The trap

Multiple bounds: class first

Join several bounds with **&. If one of them is a class, it must come first**. A comma would declare a second, separate type parameter, and | isn't allowed at all.

<T extends Number & Comparable<T>> // ✓
<T extends Comparable<T> & Number> // ✗
<T extends Number, Comparable<T>>  // ✗
💼 In the real world

Where you'll see bounds

Statistics helpers take <T extends Number>, sorting and max/min utilities take <T extends Comparable<...>>, and EnumMap<K extends Enum<K>, V> only accepts enum keys. Bounds let a library say exactly what it needs from your types, and let the compiler enforce it.

Key takeaways

  1. <T extends Number>: T must be Number or a subclass
  2. Bounds unlock the bound's methods on T
  3. extends is used for interfaces too: <T extends Comparable<T>>
  4. Multiple bounds: <T extends Number & Comparable<T>>, class first
🤯 Did you know?

A type parameter can have any number of interface bounds, but at most one class bound, and the class must come first. It's the same rule as a class that extends one class but implements many interfaces.

Practice questions

What does this print?

static <T extends Number> double sum(List<T> xs) {
    double total = 0;
    for (T x : xs) {
        total += x.doubleValue();
    }
    return total;
}
void main() {
    System.out.println(sum(List.of(1, 2.5, 3L)));
}
  1. 6
  2. 6.5
  3. Compile error
  4. Throws ClassCastException
Check your answer

6.5. Integer, Double and Long are all Numbers, so the call compiles. Each is converted with doubleValue(): 1.0 + 2.5 + 3.0 = 6.5.

What does this print?

static <T extends Number> double sum(List<T> xs) {
    return 0;
}
void main() {
    System.out.println(sum(List.of("a", "b")));
}
  1. 0.0
  2. Compile error
  3. Throws ClassCastException
  4. 0
Check your answer

Compile error. String is not a subtype of Number, so it doesn't satisfy the bound and the call is rejected at compile time.

Next: Integer is a Number. So surely a List<Integer> is a List<Number>... right? Java says no, and it's protecting you.