Skip to main content

Section 10.9 Bounded Type Parameters: Adding Constraints

Generic types and methods, as introduced in the previous sections (Box<T>, <E> void printArray(E[] arr)), provide excellent flexibility and type safety. However, by default, the type parameter (like T or E) is treated essentially as a placeholder for Object within the generic code. This means you can only reliably call methods defined in the Object class (like toString(), equals(), hashCode()) on variables of the type parameter.
But what if your generic algorithm or data structure needs to perform operations that are not defined in Object? Consider trying to write a generic method to find the larger of two items:
// Attempting a generic max method - THIS WON'T COMPILE!
public static <T> T findMaximum(T item1, T item2) {
    // if (item1 > item2) { // Compile Error! The > operator is not defined for all Objects T.
    // if (item1.compareTo(item2) > 0) { // Compile Error! Cannot assume T has compareTo().
    //     return item1;
    // } else {
    //     return item2;
    // }
    return null; // Placeholder - actual comparison logic fails
}
This code fails to compile because the compiler has no guarantee that objects of an arbitrary type T can be compared using operators like > or even that they have a specific method like compareTo(). The T could be a String, a Player, or some other type where these operations aren’t defined.
To solve this, Java generics allow us to specify bounds on type parameters. A bounded type parameter restricts the kinds of types that can be used as type arguments for that parameter. This restriction acts as a contract, guaranteeing the compiler that any supplied type will have certain required methods or characteristics (i.e., it fulfills a specific contract beyond just being an Object).

Subsection 10.9.1 Syntax: Using extends for Bounds

Bounds are specified using the extends keyword after the type parameter declaration within the angle brackets. This might seem confusing initially because extends is also used for class inheritance, but in the context of generic type parameters, extends serves a broader role, meaning "is-a-subtype-of" or "implements", essentially indicating an "is-a" relationship or required capability.
The syntax looks like this:
// Bound with a class: T must be Number or a subclass of Number
<T extends Number>

// Bound with an interface: T must implement the Comparable interface
// Note: Comparable<T> itself is generic
<T extends Comparable<T>>

// Bound for a generic class definition
public class NumericBox<N extends Number> { /* ... */ }

// Bound for a generic method definition
public static <E extends Runnable> void runTask(E task) { /* ... */ }
Key points about the extends keyword in bounds:
  • It is used for bounds involving both classes and interfaces.
  • If the bound is a class (like Number), the type argument must be that class or one of its subclasses (e.g., Integer, Double).
  • If the bound is an interface (like Comparable or Runnable), the type argument must be a class that implements that interface (e.g., String implements Comparable<String>).

Subsection 10.9.2 Example 1: Bound with a Class (Number)

Let’s say we want a generic method that can inspect any kind of Number (Integer, Double, Float, etc.) and return its value as a double. We need to call the doubleValue() method, which is defined in the abstract Number class (the superclass for all standard numeric wrapper types). We use a bound to guarantee this method exists:
public class NumericUtils {

    // Generic method constrained to Number types (or subtypes)
    public static <N extends Number> double getDoubleValue(N number) {
        // Because N extends Number, we are guaranteed the doubleValue() method exists!
        // The compiler allows this call because of the bound.
        return number.doubleValue();
    }

    public static void main(String[] args) {
        Integer i = 10;
        Double d = 25.5;
        Float f = 3.14f; // Float also extends Number

        System.out.println("Double value of Integer " + i + ": " + getDoubleValue(i));
        System.out.println("Double value of Double " + d + ": " + getDoubleValue(d));
        System.out.println("Double value of Float " + f + ": " + getDoubleValue(f));

        // Now, let's try an invalid type:
        String s = "hello";
        // System.out.println(getDoubleValue(s)); // Compile Error!
        // Error message might be:
        // "Inferred type 'String' for type parameter 'N' is not within its bound;
        // should extend 'java.lang.Number'"
    }
}
The bound <N extends Number> ensures that only subtypes of Number can be passed to getDoubleValue. Inside the method, the compiler now knows that any object of type N will have the doubleValue() method available, making the call safe. Trying to call the method with a non-Number type like String results in a clear compile-time error, preserving type safety.

Subsection 10.9.3 Example 2: Bound with an Interface (Comparable)

Now let’s fix our earlier findMaximum method. To compare two objects using compareTo, they generally need to implement the Comparable interface. We use this interface as a bound:
import java.util.Arrays; // Needed just for List example below

public class ComparisonUtils {

    // Generic method constrained to types that implement Comparable
    // Note: Comparable itself is generic! Comparable<T> ensures type safety in comparison.
    public static <T extends Comparable<T>> T findMaximum(T item1, T item2) {
        // Because T extends Comparable<T>, we know compareTo(T) exists.
        if (item1.compareTo(item2) > 0) {
            return item1; // item1 is greater
        } else {
            return item2; // item2 is greater or they are equal
        }
    }

    public static void main(String[] args) {
        // String implements Comparable<String>
        System.out.println("Max of 'apple', 'banana': " + findMaximum("apple", "banana"));

        // Integer implements Comparable<Integer>
        System.out.println("Max of 100, 50: " + findMaximum(100, 50)); // Autoboxing works

        // Player class would need to implement Comparable<Player> for this to work:
        Player p1 = new Player("Alice"); // Assume Player class exists BUT doesn't implement Comparable
        Player p2 = new Player("Bob");
        // findMaximum(p1, p2); // Compile Error!
        // Error message might be:
        // "Inferred type 'Player' for type parameter 'T' is not within its bound;
        // should implement 'java.lang.Comparable<Player>'

        // If Player implemented Comparable (see note below):
        // Player maxPlayer = findMaximum(p1, p2); // This would then compile
    }
}

Note 10.9.1. Implementing Comparable (Example).

To make the findMaximum(p1, p2) call work, the Player class would need to implement the interface, like this minimal example:
public class Player implements Comparable<Player> {
    private String name;
    // Constructor and other methods...

    public Player(String name) { this.name = name; } // Example constructor

    @Override
    public int compareTo(Player other) {
        // Compare players based on their names (alphabetically)
        return this.name.compareTo(other.name);
    }

    // toString() might be useful too
    @Override public String toString() { return name; }
}
By implementing Comparable<Player> and providing the compareTo method, Player objects now satisfy the bound required by findMaximum.
The bound <T extends Comparable<T>> guarantees that any type T passed to this method will have a compareTo method that accepts another object of the same type T. This makes the call item1.compareTo(item2) safe at compile time. Attempting to use findMaximum with a type that doesn’t implement Comparable (like our original Player class) results in a compile error, as clearly shown in the example.

Note 10.9.2. Self-Referential Bounds like Comparable<T>.

You might notice the bound uses Comparable<T>, where T refers back to the type parameter itself. This pattern is common for interfaces like Comparable to ensure type safety within the comparison method signature (i.e., you compare a String to another String, not to an Integer). The method signature int compareTo(T other) enforced by the bound guarantees this.

Subsection 10.9.4 Multiple Bounds

A type parameter can have multiple bounds, allowing you to require a type argument to satisfy several contracts simultaneously. There are specific rules for this:
  • You can specify at most one class as a bound.
  • If a class bound is present, it must be listed first in the bounds list.
  • You can specify multiple interface bounds, listed after the class bound (if any) and separated by the ampersand (&) character.
// Syntax: Class bound (optional, max 1, must be first) & Interface bounds
<T extends ClassBound & InterfaceBound1 & InterfaceBound2>

// Example: T must be a subclass of Vehicle AND implement both Runnable and Serializable
<T extends Vehicle & Runnable & Serializable>
public void processVehicleTask(T task) {
    // Inside this method, we can safely call methods from Vehicle, Runnable, and use it as Serializable
    task.accelerate(1.0); // Method from Vehicle contract
    new Thread(task).start(); // Use as Runnable
    // serializeObject(task); // Use as Serializable (assuming method exists)
}

// Example: Bound only by interfaces
<S extends Readable & java.io.Closeable>
public void readAndClose(S source) throws java.io.IOException {
     // Can safely call methods from both Readable and Closeable interfaces
     // ... read data using source.read(...) ... (method from Readable)
     source.close(); // method from Closeable
}
Why use multiple bounds? They allow you to create generic types or methods that operate on objects needing a specific combination of structural inheritance and specific capabilities. For example, our processVehicleTask needs something that is-a Vehicle but also can-do the actions defined by Runnable and Serializable. Multiple bounds provide precise compile-time guarantees for these complex requirements.

Subsection 10.9.5 Summary: Bounded Type Parameters

Bounded type parameters are essential when your generic code needs to rely on methods or properties beyond those defined in the Object class.
  • Use extends within the angle brackets (<T extends BoundType>) to specify an upper bound (a class or interface).
  • The type argument supplied when using the generic type/method must be a subtype of the class bound or implement the interface bound(s).
  • This guarantees the availability of methods from the bound type(s) within the generic code, ensuring compile-time safety for calling those methods.
  • Multiple interface bounds can be specified using &, but only one class bound is allowed, and it must come first.
Bounds allow us to write more powerful and specific generic algorithms and data structures, such as methods for numeric calculations, comparisons, sorting, or interacting with types having specific capabilities. However, bounds constrain the types that can be used. Sometimes we need the opposite: more flexibility in accepting related generic types as parameters, even if we don’t know the exact type argument. For this, we use wildcards, which we explore next.

Exercises 10.9.6 Exercises

1.

Why are bounded type parameters (e.g., <T extends Number>) necessary in some generic methods or classes?
  • To allow the generic code to work with primitive types like int.
  • Incorrect. Bounds restrict generic parameters to reference types that meet certain criteria; they don’t enable direct use of primitives.
  • To bypass type erasure at runtime.
  • Incorrect. Bounds influence compile-time checks and how erasure occurs (replacing T with the bound instead of Object), but they don’t prevent erasure.
  • To allow the generic type to be instantiated using new T().
  • Incorrect. The inability to use new T() is a limitation due to type erasure, which bounds don’t solve directly (though workarounds exist).
  • To guarantee that the type parameter T has access to specific methods beyond those defined in Object (e.g., doubleValue() or compareTo()).
  • Correct. Bounds provide a contract to the compiler that objects of type T will have the methods defined by the bound (e.g., Number or Comparable), allowing the generic code to safely call them.

2.

What does the bound <T extends Comparable<T>> mean for the type parameter T?
  • T must be a direct subclass of a class named Comparable.
  • Incorrect. Comparable is an interface, not a class, and extends is used for interface bounds here. Also, Comparable itself is generic.
  • T can be any type, but it will be compared using methods from the Object class.
  • Incorrect. The bound specifically requires T to implement the Comparable interface, guaranteeing the compareTo method.
  • T must be a type that implements the Comparable interface for comparing objects of its own type.
  • Correct. This bound ensures that any type used for T provides a compareTo(T other) method, allowing instances of T to be compared with each other.
  • T must be either the Comparable interface itself or a subclass of it.
  • Incorrect. T must be a class that *implements* the Comparable interface.

3.

Which of the following multiple bound declarations is syntactically correct according to Java’s rules?
  • <T extends Runnable & Thread>
  • Incorrect. If a class bound (Thread) is present, it must be listed first.
  • <T extends Number & Integer>
  • Incorrect. A type parameter can extend at most one class (Number or Integer, not both).
  • <T extends Number & Comparable<T> & Serializable>
  • Correct. This follows the rule: at most one class bound (Number) listed first, followed by interface bounds (Comparable, Serializable) separated by ampersands.
  • <T extends Comparable<T> , Serializable>
  • Incorrect. Multiple bounds must be separated by an ampersand (&), not a comma.
You have attempted of activities on this page.