Command Palette

Search for a command to run...

PHASE 8Intermediate Java 5+ ~34 min· topic 4 of 6

Topic 8.4

Bounded Type Parameters

In one line

A bound limits which types a type parameter accepts: <T extends Number> means "any Number or subclass", and <T extends Comparable<T>> means "any type that can compare itself". In return, your generic code may call the bound's methods, like doubleValue() or compareTo(), on values of type T.

Think of it like this

A ride at a fair says "riders must be at least 120 cm tall". It still takes many different people, but only people who meet the rule, and because of the rule the ride can safely assume every rider fits the seat belt. A bound is that sign on a type parameter: <T extends Number> lets in Integer, Double, Long and so on, and because they're all Numbers, the code inside may call doubleValue() on them.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Bound
A rule on a type parameter that limits which types it accepts, like extends Number.
Upper bound
A bound written T extends X, meaning T must be X or a subtype of X.
Multiple bounds
Several bounds joined with &, like <T extends Number & Comparable<T>>: T must satisfy all of them.
Recursive bound
A bound that mentions the type parameter itself, like <T extends Comparable<T>>: "T can be compared with T".
Comparable
The generic interface Comparable<T> with one method, int compareTo(T other), returning negative, zero or positive for less, equal or greater.
Erasure of a type variable
The single real type the compiler uses for T in the bytecode: its first bound, or Object if it has none.
Not within bounds
The compiler's complaint when a type argument doesn't satisfy a parameter's bound, like Stats<String> for Stats<T extends Number>.

Step by step

01Unbounded T only knows Object

You want a generic max(a, b). Inside it you need a.compareTo(b). But for an unbounded T, the compiler only knows T is some Object, and Object has no compareTo.

The error even tells you why: T extends Object declared in method <T>max(T,T). Every unbounded type variable is implicitly extends Object.

terminal
$ javac Main.java
── expected output ──
Main.java:3: error: cannot find symbol
return a.compareTo(b) >= 0 ? a : b;
^
symbol: method compareTo(T)
location: variable a of type T
where T is a type-variable:
T extends Object declared in method <T>max(T,T)
1 error

02Add an upper bound

<T extends Comparable<T>> promises the compiler that every T implements Comparable<T>. Now a.compareTo(b) compiles, and the method works for String, Integer, LocalDate and your own comparable classes.

Callers pay for the promise: max(new Object(), new Object()) is rejected, because Object isn't Comparable. The bound moves a check from run time (a ClassCastException inside a sort) to compile time.

Main.javawhole filejava
static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

max("pear", "apple");   // T = String  -> "pear"
max(3, 9);              // T = Integer -> 9

03Bounding a class's type parameter

Classes take bounds the same way: class Stats<T extends Number>. Inside, every T is a Number, so value.doubleValue() works. Users can write Stats<Integer> or Stats<Double>, but not Stats<String>.

extends is used for interfaces too: <T extends Runnable>, never <T implements Runnable>.

terminal
$ javac Main.java
── expected output ──
Main.java:5: error: type argument String is not within bounds of type-variable T
Stats<String> s = new Stats<>();
^
where T is a type-variable:
T extends Number declared in class Stats
Main.java:5: error: incompatible types: cannot infer type arguments for Stats<>
Stats<String> s = new Stats<>();
^
reason: inference variable T has incompatible bounds
equality constraints: String
upper bounds: Number
where T is a type-variable:
T extends Number declared in class Stats
2 errors

04Multiple bounds with &

<T extends Number & Comparable<T>> accepts types that are both: Integer, Long, Double, BigDecimal. Inside, you can call Number methods (doubleValue()) and Comparable methods (compareTo).

The rule mirrors class declarations: one class at most, listed first, then any number of interfaces. Java uses the first bound for erasure, so the order isn't cosmetic.

terminal
$ javac Main.java # <T extends Comparable<T> & Number>
── expected output ──
Main.java:2: error: interface expected here
static <T extends Comparable<T> & Number> T id(T x) { return x; }
^
1 error

05What the bound becomes in bytecode

Bounds aren't only a compile-time check: they decide the erased type. javap -s shows the real descriptors. max takes and returns Comparable; clamp, whose first bound is Number, takes and returns Number.

That's why the first bound matters: calls to the other bounds' methods need an extra checkcast to that interface inside the method.

terminal
$ javac M2.java
javap -s M2
── expected output ──
Compiled from "M2.java"
public class M2 {
public M2();
descriptor: ()V
 
static <T extends java.lang.Comparable<T>> T max(T, T);
descriptor: (Ljava/lang/Comparable;Ljava/lang/Comparable;)Ljava/lang/Comparable;
 
static <T extends java.lang.Number & java.lang.Comparable<T>> T clamp(T, T, T);
descriptor: (Ljava/lang/Number;Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;
}
What the bound becomes in bytecodediagram
Rendering diagram…

06The subclass trap: Comparable<T> vs Comparable<? super T>

class Fruit implements Comparable<Fruit> and class Apple extends Fruit. An Apple is comparable, but to Fruit, not specifically to Apple: it implements Comparable<Fruit>, never Comparable<Apple>.

So max(List<Apple>) with <T extends Comparable<T>> fails: T = Apple would need Apple implements Comparable<Apple>. The JDK's own Collections.max uses <T extends Comparable<? super T>>: "T is comparable to T or to some supertype of T". That accepts Apple.

terminal
$ javac Main.java
── expected output ──
Main.java:18: error: method max in class Main cannot be applied to given types;
Apple best = max(apples);
^
required: List<T>
found: List<Apple>
reason: inference variable T has incompatible equality constraints Fruit,Apple
where T is a type-variable:
T extends Comparable<T> declared in method <T>max(List<T>)
1 error

Try it yourself

  1. 1

    Break the Number bound

    In the first example, add System.out.println(sum(List.of("1", "2")));. Predict whether it compiles, then read the upper bounds: Number,Object and lower bounds: String lines in the error.

  2. 2

    Uncomment the strict call

    In the last example, add System.out.println(strictMax(apples)); and compile. Compare the message with the one in the walkthrough, then fix it by changing strictMax's bound to Comparable<? super T>.

  3. 3

    Swap the bound order

    In the multiple-bounds example, rewrite clamp's bound as <T extends Comparable<T> & Number>. Predict the error before compiling.

Code & diagrams

Upper bound Number: call doubleValue() on any T New tab
Sign in to run this example in your browser.

Expected output

sum marks: 245.0
sum temps: 112.0
sum views: 3.0E9
first: 70, average: 81.66666666666667
Recursive bound Comparable<T>: one max for every comparable type New tab

Version 1.10 beats 1.9 because compareTo compares numbers, not text. As strings, "1.10" would sort before "1.9".

Sign in to run this example in your browser.

Expected output

17
pear
1.10
true false
Multiple bounds: a Number that is also Comparable New tab
Sign in to run this example in your browser.

Expected output

100
2.5
0
120 is above 100 (as double: 120.0)
dosa costs 80
Comparable<? super T>: max that also works for subclasses New tab

The wildcard ? super T is explained in Topic 8.5. This is the same bound Collections.max and Collections.sort use.

Sign in to run this example in your browser.

Expected output

strict, fruits: mango(300g)
flexible, apples: fuji(180g)
flexible, fruits: mango(300g)

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Call a method the bound doesn't promise

Write static <T> T max(T a, T b) { return a.compareTo(b) >= 0 ? a : b; } with no bound.

terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: cannot find symbol
return a.compareTo(b) >= 0 ? a : b;
^
symbol: method compareTo(T)
location: variable a of type T
where T is a type-variable:
T extends Object declared in method <T>max(T,T)
1 error

Break #2

Pass a type outside the bound

With static <T extends Number> double sum(List<T> list), call sum(List.of("1", "2")).

terminal
$ javac Main.java
── what you'll see ──
Main.java:11: error: method sum in class Main cannot be applied to given types;
System.out.println(sum(List.of("1", "2")));
^
required: List<T>
found: List<String>
reason: inference variable E has incompatible bounds
upper bounds: Number,Object
lower bounds: String
where T,E are type-variables:
T extends Number declared in method <T>sum(List<T>)
E extends Object declared in method <E>of(E,E)
1 error

Break #3

Put an interface before the class in multiple bounds

Write <T extends Comparable<T> & Number>.

terminal
$ javac Main.java
── what you'll see ──
Main.java:2: error: interface expected here
static <T extends Comparable<T> & Number> T id(T x) { return x; }
^
1 error

Myth vs fact

Myth

For interface bounds you write implements: <T implements Comparable<T>>.

Fact

Bounds always use extends, for classes and interfaces alike. implements isn't allowed in a type parameter section.

Myth

<T extends Number> lets you add Integers and Doubles to the same List<T>.

Fact

T is still one specific type per use. A List<T> with T = Integer takes only Integers. To hold mixed numbers, use List<Number>.

Myth

You can write <T super Integer> for a lower bound.

Fact

Type parameters only take upper bounds. Lower bounds exist only for wildcards: List<? super Integer> (Topic 8.5).

Myth

The order of multiple bounds doesn't matter.

Fact

A class bound must come first, and the first bound is what T erases to in the bytecode, which affects binary compatibility and the casts the compiler inserts.

Interview problem

The problem

A reusable top-k method

An interviewer asks: write one method that returns the k largest items from a list, for any element type that has a natural order, including subclasses that inherit their ordering. Explain your signature.

You're given

  • Works for Integer, String, and a class Apple extends Fruit where only Fruit implements Comparable<Fruit>.
  • Doesn't modify the input list.
  • Returns the items largest first.

The interviewer follows up

01

Why not just <T extends Comparable<T>>?

02

How would you support types without a natural order?

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Changing a method's bounds changes its erased descriptor. Turning <T> T max(T, T) (descriptor uses Object) into <T extends Comparable<T>> (uses Comparable) breaks binary compatibility: old compiled callers fail with NoSuchMethodError. This is why Collections.max is declared <T extends Object & Comparable<? super T>>: the redundant Object & keeps its erasure as Object, matching the pre-generics signature.

  • ▸

    Enum<E extends Enum<E>> is the classic recursive bound. It lets Enum declare compareTo(E o) and getDeclaringClass() returning Class<E>, so Day.MONDAY.compareTo(Month.MAY) is a compile error. The same "self type" trick appears in builder hierarchies (abstract class Builder<B extends Builder<B>> with B self()) so chained setters return the subclass type.

  • ▸

    Bounds let the compiler emit direct invokeinterface Comparable.compareTo calls on the erased type, with no reflection or casts per element in the loop. The JIT then devirtualises and inlines compareTo when the call site sees one or two concrete types, so a bounded generic max is as fast as a hand-written int version for boxed values.

  • ▸

    Prefer Comparable<? super T> in public APIs (as the JDK does); Comparable<T> silently rejects subclasses that inherit their comparison. Inside private code where all types are final, the simpler form is fine.

Remember this

  1. 1

    Without a bound, a type variable's only known supertype is Object, so on a T you can call only Object methods: equals, hashCode, toString, getClass. Writing a.compareTo(b) on an unbounded T fails with cannot find symbol ... method compareTo(T). An upper bound written with extends tells the compiler more: <T extends Comparable<T>> makes compareTo(T) available.

  2. 2

    In a bound, extends means "is a subtype of", for classes and interfaces. You never write implements there: <T extends Runnable> is correct even though Runnable is an interface. The bound checks every use: Stats<String> with class Stats<T extends Number> fails with type argument String is not within bounds of type-variable T.

  3. 3

    A type parameter can have several bounds joined by &: <T extends Number & Comparable<T>> means "a Number that can also compare itself" (like Integer). At most one bound may be a class, and it must come first; the rest must be interfaces. Putting an interface first and a class second gives interface expected here.

  4. 4

    <T extends Comparable<T>> is a recursive bound (also called F-bounded): T appears inside its own bound. It reads "T is comparable to other T's". The JDK uses the same idea in class Enum<E extends Enum<E>>, which is how every enum's compareTo accepts only its own constants. The fully flexible version is <T extends Comparable<? super T>>, which also accepts a subclass like Apple whose compareTo was inherited from Fruit. You'll understand the ? super part in Topic 8.5.

  5. 5

    The bound also decides what T becomes after compilation. An unbounded T erases to Object; <T extends Comparable<T>> erases to Comparable; <T extends Number & Comparable<T>> erases to the first bound, Number. javap -s shows these erased types in the method descriptors. Topic 8.6 builds on this.

  6. 6

    There is no lower bound for a type parameter: <T super Integer> isn't legal Java. Lower bounds exist only for wildcards (? super Integer, Topic 8.5). A good rule: use a bound when the generic code needs to call methods of the bound or relate types (T compared to T); otherwise leave T unbounded so the code works for more types.

Explain it without notes

01

What does an upper bound like <T extends Number> do for the caller and for the code inside the method?

02

Explain <T extends Comparable<T>>. Why is it called a recursive bound, and what problem does Comparable<? super T> solve?

03

What are the rules for multiple bounds, and why does the order matter?

04

How does a bound affect the bytecode of a generic method?

Practice

01

Write static <T extends Comparable<? super T>> T min(List<T> list) and test it with integers and strings.

02

Write a class Range<T extends Comparable<? super T>> with contains(T value) and test it with Range<Integer>(1, 10) and Range<String>("b", "m").

03

Write static <T extends Number> double[] toDoubles(List<T> list) and print the result for List.of(1, 2, 3) with Arrays.toString.

Trade-offs

  • ↔

    A bound makes the method more powerful inside (you can call the bound's methods) but less widely usable outside (fewer types qualify). Use the weakest bound that lets the code work.

  • ↔

    Comparable<? super T> is more correct for public APIs but harder to read than Comparable<T>. Many teams use the simple form internally and the JDK form in shared libraries.

  • ↔

    Bounded generics versus taking a Comparator parameter: a bound relies on the type's natural order and is concise; a comparator works for any type and any order. Libraries usually offer both.

Done when you can

  • Done when you can explain why an unbounded T only has Object methods.

  • Done when you can write <T extends Number> and <T extends Comparable<? super T>> methods.

  • Done when you can write multiple bounds in the legal order.

  • Done when you can read a "not within bounds" error and fix it.

  • Done when you can say what a bounded type variable erases to.

  • Done when you can explain the Apple extends Fruit problem with Comparable<T>.