☕ Java

Bounded Types

Bounded type parameters constrain which concrete types may be substituted for a type parameter. An upper bound (T extends Number) restricts T to Number and its subtypes, making Number's methods available on values of type T within the generic code. A lower bound cannot be applied to type parameters (only to wildcards), but multiple upper bounds can be combined with & (T extends Number & Comparable<T> & Serializable). Bounds serve two purposes simultaneously: they restrict the valid type arguments that callers can supply, and they expand what the generic code can do with values of type T by exposing the bound's API. Without a bound, only Object's methods are available on T values. This entry covers single and multiple upper bounds, recursive bounds, bounds on class type parameters vs method type parameters, how bounds interact with type erasure, bridge methods generated by the compiler, and practical patterns where bounds enable algorithms impossible with unbounded type parameters.

Single Upper Bound — Restricting and Enabling

An upper bound is declared with the extends keyword: <T extends SomeType>. Despite the keyword, T may be substituted by SomeType itself or any of its subtypes — for an interface bound, any type that implements SomeType. The extends keyword is overloaded in this context: it means "is a subtype of" (subtypes of a class or implementors of an interface), not only "directly extends." The bound serves two roles simultaneously. The restriction role: at a call site or instantiation site, the compiler rejects any type argument that is not a subtype of the bound. Passing a String where <T extends Number> is expected is a compile-time error. The enablement role: inside the generic class or method body, the compiler knows that any value of type T has all the methods declared in the bound, so those methods can be called on T values. Without a bound, only Object's methods are available, because that is the only thing guaranteed about any reference type. Bounds apply to class type parameters and method type parameters identically. For a class, the bound applies throughout the class body. For a method, it applies throughout the method's parameter list, return type, and body. The bound is part of the type parameter declaration — not of the type argument — so the bound is a constraint on what callers may supply, visible to both the caller and the implementor. When the bound is an interface, the type parameter behaves polymorphically with respect to that interface. <T extends Runnable> means you can call t.run() on any T within the body. <T extends Iterable<String>> means you can use T in an enhanced-for loop. This is the mechanism that makes generic algorithms possible: write once, work for all types that satisfy the contract expressed by the bound.
Java
// ── Single upper bound — class ────────────────────────────────────────
public class NumberBox<T extends Number> {
    private T value;

    public NumberBox(T value) { this.value = value; }

    public T get() { return value; }

    // Number's methods are available because of the bound:
    public double asDouble()  { return value.doubleValue(); }
    public int    asInt()     { return value.intValue(); }
    public long   asLong()    { return value.longValue(); }

    public boolean isPositive() { return value.doubleValue() > 0; }
}

NumberBox<Integer> intBox  = new NumberBox<>(42);
NumberBox<Double>  dblBox  = new NumberBox<>(3.14);
NumberBox<Long>    longBox = new NumberBox<>(1_000_000L);

System.out.println(intBox.asDouble());   // 42.0
System.out.println(dblBox.isPositive()); // true

// COMPILE ERROR — String is not a subtype of Number:
// NumberBox<String> strBox = new NumberBox<>("hello");

// ── Single upper bound — method ───────────────────────────────────────
public static <T extends Comparable<T>> T clamp(T value, T min, T max) {
    // compareTo() available because T extends Comparable<T>:
    if (value.compareTo(min) < 0) return min;
    if (value.compareTo(max) > 0) return max;
    return value;
}

System.out.println(clamp(5, 1, 10));       // 5
System.out.println(clamp(15, 1, 10));      // 10
System.out.println(clamp("dog", "ant", "fox")); // dog

// ── Upper bound on an interface ────────────────────────────────────────
public static <T extends Iterable<String>> void printAll(T source) {
    for (String s : source) {       // enhanced-for works because T extends Iterable
        System.out.println(s);
    }
}

printAll(List.of("alpha", "beta", "gamma"));    // List implements Iterable
printAll(Set.of("x", "y", "z"));               // Set implements Iterable
// printAll(new int[]{1,2,3});  // COMPILE ERROR — int[] does not implement Iterable

// ── Bound enables calling interface methods ───────────────────────────
public static <T extends AutoCloseable> void useAndClose(T resource) throws Exception {
    try {
        System.out.println("Using: " + resource);
    } finally {
        resource.close();  // close() available because T extends AutoCloseable
    }
}

// ── Without bound — only Object methods available ─────────────────────
public static <T> void printLength(T value) {
    // value.length()   // COMPILE ERROR — T is unbounded, only Object methods
    System.out.println(value.toString());   // toString() from Object — always OK
    System.out.println(value.hashCode());   // hashCode() from Object — always OK
}

Multiple Bounds and Recursive Bounds

A type parameter can have multiple upper bounds combined with &: <T extends ClassBound & Interface1 & Interface2>. The rules are: at most one class bound is allowed and it must appear first; any number of interface bounds may follow. The resulting type parameter must be substituted with a type that satisfies all bounds simultaneously. Inside the generic body, all methods from all bounds are available on T values. Multiple bounds are most commonly used when an algorithm requires both a specific API and a standard capability: <T extends Number & Comparable<T>> for numeric types that can be sorted, <T extends Serializable & Cloneable> for types that support both capabilities, or <T extends AbstractWidget & Configurable & Closeable> in framework code. When a class type parameter has multiple bounds, type erasure replaces T with the leftmost bound in the bytecode. If the leftmost bound is a class, T erases to that class. If it is an interface (only possible when there is no class bound), T erases to that interface. This means that if you write <T extends Comparable<T> & Serializable>, T erases to Comparable in bytecode, and any access through the Serializable interface requires a synthetic cast in the bytecode. If you write <T extends Number & Comparable<T>>, T erases to Number. A recursive type bound is one where the type parameter appears within its own bound: <T extends Comparable<T>>. This expresses that T values can be compared to other T values — the type is self-comparable. The canonical example is the signature of Collections.sort: <T extends Comparable<? super T>>. The recursive structure threads the concrete type through the bound so that compareTo accepts the correct type at the call site, not just Object. Enum<E extends Enum<E>> is the most extreme example, ensuring that each enum type's compareTo and ordinal methods are correctly typed to that specific enum.
Java
// ── Multiple bounds ───────────────────────────────────────────────────
// T must be both a Number and ComparableInteger, Long, Double qualify
public static <T extends Number & Comparable<T>> T clampNumeric(
        T value, T min, T max) {
    if (value.compareTo(min) < 0) return min;   // Comparable bound
    if (value.compareTo(max) > 0) return max;   // Comparable bound
    System.out.printf("%.2f in [%.2f, %.2f]%n",
        value.doubleValue(),    // Number bound
        min.doubleValue(),      // Number bound
        max.doubleValue());     // Number bound
    return value;
}

clampNumeric(5, 1, 10);      // 5 in [1.00, 10.00]
clampNumeric(3.14, 0.0, 2.0); // returns 2.0

// COMPILE ERROR — String is Comparable but not Number:
// clampNumeric("hello", "aaa", "zzz");

// ── Class bound must come first ────────────────────────────────────────
// OK — class first, then interfaces:
class MultiBox<T extends Number & Comparable<T> & Serializable> {
    private T value;
    public MultiBox(T v) { this.value = v; }
    public T get()       { return value; }
    // doubleValue() from Number, compareTo() from Comparable, ready for serialization
}

// COMPILE ERROR — two class bounds not allowed:
// class Bad<T extends Number & Integer> {}    // error: cannot extend both

// COMPILE ERROR — class not first:
// class Bad<T extends Comparable<T> & Number> {}  // error: Number is a class, must be first

// ── Recursive type bound — self-comparable ────────────────────────────
// The classic form: T can compare itself to other T values
public static <T extends Comparable<T>> T max(List<T> list) {
    if (list.isEmpty()) throw new NoSuchElementException();
    T result = list.get(0);
    for (T element : list) {
        if (element.compareTo(result) > 0) result = element;
    }
    return result;
}

System.out.println(max(List.of(3, 1, 4, 1, 5, 9)));          // 9
System.out.println(max(List.of("banana", "apple", "cherry"))); // cherry

// ── Enum's recursive bound in practice ────────────────────────────────
// Enum<E extends Enum<E>> — each enum type refers to itself:
enum Planet { MERCURY, VENUS, EARTH, MARS }

// Because Planet extends Enum<Planet>:
Planet[] planets = Planet.values();
Arrays.sort(planets);  // works because Enum<E> implements Comparable<E>
System.out.println(Arrays.toString(planets));

// ── Builder pattern with recursive bound ─────────────────────────────
// Allows fluent chaining through inheritance:
abstract class Builder<T, B extends Builder<T, B>> {
    protected String name;

    @SuppressWarnings("unchecked")
    public B withName(String name) {
        this.name = name;
        return (B) this;   // safe: B is always the concrete subclass
    }

    public abstract T build();
}

class PersonBuilder extends Builder<Person, PersonBuilder> {
    private int age;

    public PersonBuilder withAge(int age) {
        this.age = age;
        return this;          // returns PersonBuilder, not Builder
    }

    @Override public Person build() { return new Person(name, age); }
}

Person p = new PersonBuilder()
    .withName("Alice")        // returns PersonBuilder (not just Builder)
    .withAge(30)              // PersonBuilder-specific method
    .build();

// ── Type erasure with multiple bounds ────────────────────────────────
// <T extends Number & Comparable<T>>:
//   T erases to Number (leftmost bound, which is the class)
//   Access through Comparable interface gets synthetic cast in bytecode

// <T extends Comparable<T> & Serializable>:
//   T erases to Comparable (leftmost bound, which is an interface)
//   Access through Serializable gets synthetic cast in bytecode

// This is why putting the class first matters: it controls what T erases to,
// which affects performance (avoids extra casts for the most-used bound):
// Prefer: <T extends Number & Comparable<T>>
// Over:   <T extends Comparable<T> & Number>   (Number access costs a cast)

Bridge Methods and Bounds in Inheritance

When a class implements a generic interface or extends a generic class with a bounded type parameter, the compiler may generate bridge methods — synthetic methods that maintain polymorphism after type erasure. Bridge methods are an implementation detail of the JVM's support for generics, but understanding them explains several otherwise-surprising behaviors: why getClass() sometimes returns unexpected method signatures, why certain override patterns behave unexpectedly, and why binary compatibility is preserved across erasure. Consider a class Box<T extends Number> with method T get(). After erasure, T becomes Number, so the bytecode method is Number get(). If a subclass DoubleBox extends Box<Double> and overrides get() to return Double, the bytecode has two methods: Double get() (the override) and Number get() (a bridge, generated by the compiler, that calls Double get() and returns the result). The bridge is needed because callers compiled against Box<T extends Number> expect a Number get() method — without the bridge, polymorphism through the erased type would fail. Bounds also interact with covariant return types. Java allows overriding a method to narrow the return type (covariant return), and when combined with generics, bridge methods handle the covariant case automatically. The Bridge pattern is entirely transparent to the programmer in normal usage but surfaces when using reflection: Method.isBridge() returns true for bridge methods, and they should be excluded from reflective method listing when you only want user-declared methods. A bound on a class type parameter is inherited by all methods of the class. If you declare class Sorter<T extends Comparable<T>>, every method can use T's compareTo(). If a subclass UncheckedSorter<T> extends Sorter<T> without re-stating the bound, T still has the Comparable<T> bound — inherited from the superclass. The subclass does not need to and should not repeat the bound unless it wants to tighten it further.
Java
// ── Bridge method generation ──────────────────────────────────────────
public class NumberContainer<T extends Number> {
    protected T value;

    public NumberContainer(T value) { this.value = value; }

    // After erasure: Number getValue() — T erased to bound (Number)
    public T getValue() { return value; }
}

// Subclass fixes T = Double:
public class DoubleContainer extends NumberContainer<Double> {
    public DoubleContainer(Double value) { super(value); }

    // Covariant override — returns Double instead of Number:
    @Override
    public Double getValue() { return value; }
    // Compiler also generates: (bridge) Number getValue() { return this.getValue(); }
    // The bridge delegates to the real Double getValue()
}

DoubleContainer dc = new DoubleContainer(3.14);
System.out.println(dc.getValue());  // 3.14 (Double)

// Polymorphism works through the erased type:
NumberContainer<Double> nc = dc;
Number n = nc.getValue();           // calls bridge → calls Double getValue()
System.out.println(n.getClass());   // class java.lang.Double

// ── Observing bridge methods via reflection ───────────────────────────
for (Method m : DoubleContainer.class.getDeclaredMethods()) {
    System.out.printf("%-20s bridge=%-5b synthetic=%b%n",
        m.getName() + "()" + m.getReturnType().getSimpleName(),
        m.isBridge(),
        m.isSynthetic());
}
// getValue()Double    bridge=false synthetic=false   ← your override
// getValue()Number    bridge=true  synthetic=true    ← compiler-generated bridge

// ── Inherited bounds — subclass need not repeat them ──────────────────
class Sorter<T extends Comparable<T>> {
    public T findMin(List<T> list) {
        return list.stream()
            .min(Comparator.naturalOrder())  // works: T has compareTo from bound
            .orElseThrow();
    }
}

// T still has the Comparable<T> bound — inherited:
class ReverseSorter<T extends Comparable<T>> extends Sorter<T> {
    public T findMax(List<T> list) {
        return list.stream()
            .max(Comparator.naturalOrder())  // T still has compareTo
            .orElseThrow();
    }
}

ReverseSorter<String> rs = new ReverseSorter<>();
System.out.println(rs.findMax(List.of("banana", "apple", "cherry"))); // cherry
System.out.println(rs.findMin(List.of("banana", "apple", "cherry"))); // apple

// ── Bound tightening in subclass ──────────────────────────────────────
class NumericSorter<T extends Number & Comparable<T>> extends Sorter<T> {
    // Tightened: T must also be a Number (Sorter only required Comparable)
    public double sumAll(List<T> list) {
        return list.stream()
            .mapToDouble(Number::doubleValue)  // Number bound from this class
            .sum();
    }
}

NumericSorter<Integer> ns = new NumericSorter<>();
System.out.println(ns.sumAll(List.of(1, 2, 3, 4, 5)));  // 15.0
System.out.println(ns.findMin(List.of(3, 1, 4)));        // 1

Related Topics in Generics

Generic Class
A generic class in Java is a class parameterized by one or more type parameters, written as class Box<T> or class Pair<K, V>. Type parameters allow a single class definition to work correctly with many different types while preserving full compile-time type safety — eliminating the need for casts, preventing ClassCastException at runtime, and making the intended type contract explicit in the API. The compiler erases type parameters at bytecode level (type erasure), replacing them with their bounds or Object, but all type checking occurs at compile time before erasure. This entry covers type parameter declaration and naming conventions, bounded type parameters (extends, super), multiple type parameters, raw types and why they are unsafe, type erasure and its consequences, wildcards (?, ? extends T, ? super T), the PECS principle, recursive type bounds, generic interfaces, inheritance with generic classes, and how to design generic classes that work correctly across all parameterizations.
Generic Method
A generic method is a method that declares its own type parameters, independent of any type parameters on its enclosing class. The type parameters appear in angle brackets before the return type: public static <T> T identity(T value). Generic methods can appear in non-generic classes, in generic classes (adding their own type parameters beyond the class's), and as both static and instance methods. The compiler infers the type arguments for generic method calls from the argument types, eliminating the need for explicit type witness syntax in most cases. This entry covers type parameter declaration on methods, type inference rules and when inference fails, bounded type parameters on methods, returning computed type relationships, generic methods that operate on multiple type parameters, static generic utility methods (the standard library pattern), the interaction between method type parameters and class type parameters, explicit type witness syntax, and the difference between using a class type parameter vs a method type parameter.
Type Parameters
Type parameters are the placeholders — written as single uppercase letters like T, E, K, V — that make Java generics work. A type parameter declares a variable that stands in for a concrete type, to be supplied by the caller or inferred by the compiler. They appear on classes (class Box<T>), interfaces (interface Comparable<T>), and methods (public static <T> T identity(T value)), and they are the mechanism by which the Java type system achieves compile-time type safety without duplicating code for each concrete type. This entry covers where and how type parameters are declared, the naming conventions and why they exist, the scope rules for type parameters (where they are in scope and where they are not), how type parameters interact with inheritance, how the compiler resolves a type parameter to a concrete type at each call site, and the distinction between a type parameter (a declaration) and a type argument (a concrete substitution).
Wildcards
Wildcards — written as ? — represent an unknown type in a generic type argument position. Where a type parameter T is a name given to an unknown type that can be referenced throughout a declaration, a wildcard ? is an anonymous unknown type used at the point of consumption. Wildcards appear in method parameter types, field types, return types, and variable declarations — anywhere a parameterized type appears as a type (not where a type is being declared). The three wildcard forms are: the unbounded wildcard (List<?>), the upper-bounded wildcard (List<? extends Number>), and the lower-bounded wildcard (List<? super Integer>). Wildcards are the mechanism that introduces covariance and contravariance into Java's otherwise invariant generic type system. This entry covers all three forms in depth, the producer-extends consumer-super (PECS) principle, wildcard capture and the capture helper pattern, nested wildcards, and when to use wildcards vs type parameters.