🧪 Generics · Intermediate

Recursive bounds in Java

<T extends Comparable<? super T>> as used by Collections.max.

🧩 The mysteryCollections.max is declared as <T extends Object & Comparable<? super T>> T max(Collection<? extends T>). Scary? Let's decode it one piece at a time.

A type that mentions itself

**<T extends Comparable<T>> is a recursive bound: T appears inside its own bound. It means "T can be compared with other Ts**", so generic code can call compareTo with no casts.

static <T extends Comparable<T>> T max(
        List<T> xs) {
    T best = xs.get(0);
    for (T x : xs)
        if (x.compareTo(best) > 0) best = x;
    return best;
}
🔮 Predict it

Your turn

Using that max, what does this print?

static <T extends Comparable<T>> T max(
        List<T> xs) {
    T best = xs.get(0);
    for (T x : xs)
        if (x.compareTo(best) > 0) best = x;
    return best;
}
void main() {
    IO.println(max(List.of("cat", "dog", "ant")));
}
  1. cat
  2. dog
  3. ant
  4. Compile error
Show the answer

String implements Comparable<String>, so it fits the bound. Alphabetically ant < cat < dog.

The inheritance snag

Fruit implements Comparable<Fruit>, and Apple extends Fruit. Apple **inherits compareTo(Fruit), so Apple is a Comparable<Fruit>**, not a Comparable<Apple>. With T = Apple, the bound Comparable<T> isn't satisfied.

class Fruit implements Comparable<Fruit> {
    public int compareTo(Fruit o) { return 0; }
}
class Apple extends Fruit {}
// Apple is Comparable<Fruit>, not <Apple>
🔮 Predict it

Apples to apples?

Same simple bound, now with Apples. What happens?

class Fruit implements Comparable<Fruit> {
    public int compareTo(Fruit o) { return 0; }
}
class Apple extends Fruit {}
static <T extends Comparable<T>> T first(
        List<T> xs) { return xs.get(0); }
void main() {
    IO.println(first(new ArrayList<Apple>()));
}
  1. Prints null
  2. Compile error
  3. Throws IndexOutOfBoundsException
Show the answer

With T = Apple, the bound demands Comparable<Apple>, but Apple only has the inherited Comparable<Fruit>. Inference fails: compile error.

Fixing the bound

✗ Too strict
<T extends Comparable<T>>

Rejects types that inherit compareTo from a superclass, like Apple.

✓ Flexible
<T extends Comparable<? super T>>

"T can be compared with T or one of its supertypes": matches Apple's inherited compareTo(Fruit). This is PECS again: compareTo consumes T.

Decoding Collections.max

Now you can read it: T must be comparable to itself or a supertype (Comparable<? super T>), and the collection may hold any subtype (? extends T). The extra Object & makes T erase to Object, so the method keeps its old pre-generics signature.

static <T extends Object
            & Comparable<? super T>>
    T max(Collection<? extends T> coll)
💼 In the real world

Where you'll meet it

Sorting and max/min utilities, TreeMap-style containers, and fluent builders that return their own subtype (Builder<B extends Builder<B>>) all use recursive bounds. Being able to read them makes library source code, and senior interviews, much less intimidating.

Key takeaways

  1. <T extends Comparable<T>>: T compares with itself
  2. <T extends Comparable<? super T>>: also accepts subclasses of comparable classes
  3. Enum<E extends Enum<E>> uses the same trick
  4. Lets generic code call compareTo safely without casts
🤯 Did you know?

Java's own Enum class is declared as Enum<E extends Enum<E>>: a recursive bound. It's how every enum gets a compareTo that only accepts constants of the same enum type.

Practice questions

What does this print?

static <T extends Comparable<T>> T max(List<T> xs) {
    T best = xs.get(0);
    for (T x : xs)
        if (x.compareTo(best) > 0) best = x;
    return best;
}
void main() {
    var words = List.of("pear", "fig", "plum");
    System.out.println(max(words));
}
  1. pear
  2. plum
  3. fig
  4. Compile error
Check your answer

plum. String implements Comparable<String>, so it fits the bound. Alphabetically "plum" > "pear" > "fig", so plum is the maximum.

Why does Collections.max use Comparable<? super T> instead of Comparable<T>?

  1. To allow comparing Strings with Integers
  2. So types that inherit compareTo from a superclass — e.g. a subclass of a Comparable<Fruit> class — still qualify
  3. To make max run faster
  4. Because Comparable can't be parameterized with T
Check your answer

So types that inherit compareTo from a superclass — e.g. a subclass of a Comparable<Fruit> class — still qualify. If Apple extends Fruit and Fruit implements Comparable<Fruit>, then Apple is a Comparable<Fruit>, not a Comparable<Apple>. ? super T accepts that.

Next world: Lambdas! How can you pass a whole block of behaviour into a method in a single line, like x -> x * 2?