☕ Java

Upper Bound

An upper bound in Java generics restricts a type parameter or wildcard to a specific type and all its subtypes. It is declared with the extends keyword in two distinct contexts: on a type parameter (class Box<T extends Number>) and on a wildcard (List<? extends Number>). Both forms mean 'this type must be Number or any subtype of Number,' but they differ fundamentally in usage: bounded type parameters name the variable and allow it to be referenced throughout a declaration, while bounded wildcards appear at the use site and keep the type anonymous. This entry covers both forms in full, how the bound expands the available API within the generic body, multiple upper bounds on type parameters (T extends A & B & C), the precise subtyping rules introduced by upper-bounded wildcards, how upper bounds interact with type erasure (determining what T erases to), the difference between <T extends Number> and <? extends Number> and when each is appropriate, and practical patterns where upper bounds are the enabling mechanism.

Upper Bound on Type Parameters — T extends SomeType

An upper bound on a type parameter is declared as <T extends UpperBound> in the angle brackets of a class, interface, or method declaration. The bound may be a class, an interface, or another type parameter. The extends keyword here means "is a subtype of" in the broadest sense: for a class bound it means the concrete type must be the bound class or any of its subclasses; for an interface bound it means the concrete type must implement the interface; for a type parameter bound it creates a recursive or relative bound between two parameters. Inside the generic body, the bound makes the bound type's entire API available on values of type T. A variable of type T can be used anywhere a variable of the bound type can be used. All methods declared in the bound, all fields accessible from the bound, and all interfaces the bound implements are accessible through T. If the bound is Comparable<T>, then compareTo(T other) is available. If the bound is Number, then doubleValue(), intValue(), longValue(), floatValue(), and byteValue() are all available. A critical nuance: an upper bound does not mean "T is exactly the bound type." It means "T is the bound type or something more specific." Inside <T extends List<String>>, T might be ArrayList<String>, LinkedList<String>, or any other List<String> implementation. The methods available inside the generic body are those of List<String>, not those of a specific List implementation. You cannot call ArrayList-specific methods on T even if you know in a specific call that T = ArrayList<String>. The upper bound also restricts what type arguments are valid at call sites. If a class is declared as class Repository<T extends Entity>, then Repository<String> is a compile error — String does not extend Entity. This enforcement protects the generic body: the code inside can rely on Entity's methods being available, which would be violated if String were allowed.
Java
// ── Upper bound on class type parameter ──────────────────────────────
public class NumberRange<T extends Number & Comparable<T>> {
    private final T min;
    private final T max;

    public NumberRange(T min, T max) {
        // compareTo() available from Comparable<T> bound:
        if (min.compareTo(max) > 0) throw new IllegalArgumentException("min > max");
        this.min = min;
        this.max = max;
    }

    public boolean contains(T value) {
        return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
    }

    public double span() {
        // doubleValue() available from Number bound:
        return max.doubleValue() - min.doubleValue();
    }

    public T getMin() { return min; }
    public T getMax() { return max; }
}

NumberRange<Integer> intRange = new NumberRange<>(1, 10);
System.out.println(intRange.contains(5));  // true
System.out.println(intRange.contains(15)); // false
System.out.println(intRange.span());       // 9.0

NumberRange<Double> dblRange = new NumberRange<>(0.0, 1.0);
System.out.println(dblRange.contains(0.5)); // true
System.out.println(dblRange.span());        // 1.0

// COMPILE ERROR — String satisfies neither Number nor Comparable<String> in this combination:
// Correction: String IS Comparable<String> but NOT Number:
// NumberRange<String> strRange = new NumberRange<>("a", "z");  // COMPILE ERROR (not Number)

// ── Upper bound makes bound's methods available ────────────────────────
public static <T extends Comparable<T>> int countGreaterThan(List<T> list, T threshold) {
    int count = 0;
    for (T element : list) {
        if (element.compareTo(threshold) > 0) count++; // compareTo available from bound
    }
    return count;
}

System.out.println(countGreaterThan(List.of(1,5,3,8,2), 4));        // 2 (5 and 8)
System.out.println(countGreaterThan(List.of("a","m","z","b"), "k")); // 2 (m and z)

// ── Upper bound on method type parameter ─────────────────────────────
public static <T extends AutoCloseable> void withResource(T resource, Consumer<T> action)
        throws Exception {
    try {
        action.accept(resource);   // Consumer<T> accepts T
    } finally {
        resource.close();          // close() available from AutoCloseable bound
    }
}

withResource(new FileInputStream("file.txt"), stream -> {
    // process stream — typed as FileInputStream, not just AutoCloseable
});

// ── Bound restricts valid type arguments at call sites ─────────────────
public class EventHandler<T extends Event> {
    private final List<Consumer<T>> listeners = new ArrayList<>();

    public void addListener(Consumer<T> listener) { listeners.add(listener); }

    public void fire(T event) {
        event.timestamp();  // Event method available from bound
        listeners.forEach(l -> l.accept(event));
    }
}

EventHandler<MouseEvent> mouseHandler = new EventHandler<>();
// EventHandler<String> bad = new EventHandler<>();  // COMPILE ERROR — String not an Event

Upper Bound on Wildcards — ? extends T and Covariance

An upper-bounded wildcard ? extends T in a type position such as List<? extends Number> introduces covariance into what would otherwise be an invariant generic type. Without the wildcard, List<Integer> is not a subtype of List<Number> even though Integer is a subtype of Number. With the wildcard, List<Integer> is a subtype of List<? extends Number>. The wildcard opens the type to accept any parameterization within the specified hierarchy. The practical effect on reading: from a List<? extends Number>, get() returns Number. The actual runtime type might be Integer, Double, or any other Number subtype, but the compiler only guarantees Number at the point of the get() call. This is sufficient to call any Number method on the result. Reading is type-safe and useful. The practical effect on writing: a List<? extends Number> cannot receive any typed element through add(), set(), or similar mutating methods. The reason: if the actual runtime type is List<Integer> and you call add(3.14) (a Double), you have inserted a Double into an Integer list, violating type safety. The compiler prevents this by rejecting all adds except null. This asymmetry — safe reads, forbidden writes — defines the upper-bounded wildcard's contract. The subtyping relationships introduced are precise: if B extends A, then List<B> is a subtype of List<? extends A>. List<A> is also a subtype of List<? extends A> (A extends A is trivially true). List<? extends B> is a subtype of List<? extends A> (because any type that is ? extends B also satisfies ? extends A). This transitivity is what makes upper-bounded wildcards so powerful for read-only APIs: a method accepting List<? extends Comparable<?>> accepts virtually any list whose elements can be compared. Return types with upper-bounded wildcards are generally discouraged. A method returning List<? extends Number> forces callers to deal with the wildcard type — they cannot add elements to it, and if they need to call a method that requires a List<Number>, they must go through a copy. Return types are better as specific types (List<Number>, List<T> where T is a named type parameter) so callers have maximum flexibility with the returned value.
Java
// ── ? extends T introduces covariance ────────────────────────────────
List<Integer> integers = List.of(1, 2, 3);
List<Double>  doubles  = List.of(1.1, 2.2, 3.3);
List<Number>  numbers  = List.of(10, 20.0, 30L);

// Without wildcard — invariant, none of these work:
// List<Number> n1 = integers;  // COMPILE ERROR
// List<Number> n2 = doubles;   // COMPILE ERROR

// With upper-bounded wildcard — covariant, all work:
List<? extends Number> w1 = integers;   // OK
List<? extends Number> w2 = doubles;    // OK
List<? extends Number> w3 = numbers;    // OK

// ── Reading from ? extends is typed ──────────────────────────────────
public static void analyzeNumbers(List<? extends Number> list) {
    double sum = 0, min = Double.MAX_VALUE, max = Double.MIN_VALUE;
    for (Number n : list) {             // n is Number — all Number methods available
        double d = n.doubleValue();     // from Number
        sum += d;
        if (d < min) min = d;
        if (d > max) max = d;
    }
    System.out.printf("sum=%.1f min=%.1f max=%.1f%n", sum, min, max);
}

analyzeNumbers(integers);  // sum=6.0 min=1.0 max=3.0
analyzeNumbers(doubles);   // sum=6.6 min=1.1 max=3.3

// ── Writing to ? extends is forbidden ────────────────────────────────
List<? extends Number> mixed = new ArrayList<>(integers);
// mixed.add(42);    // COMPILE ERROR — might be List<Double>
// mixed.add(3.14);  // COMPILE ERROR — might be List<Integer>
// mixed.add(null);  // OK — null is always safe

// ── Subtype relationships ──────────────────────────────────────────────
// If B extends A: List<B> is subtype of List<? extends A>
List<Integer> listInt  = List.of(1, 2, 3);
List<? extends Number>  asNumber  = listInt;   // Integer extends Number ✓
List<? extends Object>  asObject  = listInt;   // Integer extends Object
List<? extends Integer> asInteger = listInt;   // Integer extends Integer

// List<? extends B> is subtype of List<? extends A> when B extends A:
List<? extends Integer> wildcardInt = List.of(1, 2, 3);
List<? extends Number>  wildcardNum = wildcardInt;   // OK — transitivity ✓

// ── Upper-bounded wildcard in method parameters ───────────────────────
// Single method serves List<Integer>, List<Double>, List<BigDecimal>, etc.:
public static double average(List<? extends Number> list) {
    if (list.isEmpty()) return 0;
    return list.stream()
               .mapToDouble(Number::doubleValue)
               .average()
               .orElse(0);
}

System.out.println(average(List.of(1, 2, 3, 4, 5)));      // 3.0
System.out.println(average(List.of(1.5, 2.5, 3.5)));      // 2.5
System.out.println(average(List.of(100L, 200L, 300L)));    // 200.0

// ── When ? extends T vs <T extends UpperBound> ───────────────────────
// USE ? extends when the type name isn't needed in the method body or return:
public static double sum(List<? extends Number> list) {    // type name not needed
    return list.stream().mapToDouble(Number::doubleValue).sum();
}

// USE <T extends> when the type name appears in multiple positions:
public static <T extends Comparable<T>> T max(List<T> list) {  // T in return type
    return list.stream().max(Comparator.naturalOrder()).orElseThrow();
}
// If this were written with ?, the return type would be ? extends Comparable<?> — unusable.

Upper Bounds and Type Erasure — What T Erases To

Type erasure replaces every type parameter with its erasure in the compiled bytecode. For an unbounded type parameter T, the erasure is Object. For a bounded type parameter T extends SomeType, the erasure is SomeType — specifically the leftmost bound when multiple bounds are declared. This means that the upper bound determines what bytecode type is used for all T-typed variables, parameters, and return values after compilation. The erasure determines the cast that the compiler inserts at use sites. If T extends Number and a method returns T, the caller receives a Number in bytecode and no cast is needed there. But if the caller assigns the result to a variable of a more specific type (Integer variable = box.get() where Box<Integer> is the type at the call site), the compiler inserts a checkcast instruction to Integer in the bytecode. This cast is generated automatically and is what makes get() on a Box<Integer> return Integer without requiring an explicit (Integer) cast in source code. For multiple bounds, the leftmost bound is the erasure. <T extends Number & Comparable<T>>: T erases to Number. Access through the Number interface is direct in bytecode; access through the Comparable interface requires a synthetic cast (the compiler inserts these automatically where needed). This is why the ordering of multiple bounds matters for performance in tight loops: place the most frequently accessed bound first to minimize the number of synthetic casts in the bytecode. For wildcards, ? extends Number also erases to Number in the contexts where ? is used as a type. But wildcards don't have an erasure in quite the same sense as type parameters because they are not named variables — they're constraints on use sites. The effect is similar: a get() on a List<? extends Number> returns Number in the source code, and Number in the bytecode. Understanding erasure is essential for understanding why certain patterns require @SuppressWarnings("unchecked"): when you cast to a parameterized type like (List<Integer>), the JVM cannot actually check the Integer part (only List is checkable), so the compiler warns that the cast is only partially verified. The upper bound determines what the JVM can check; the parameterization beyond the erasure is the unchecked part.
Java
// ── Erasure of T extends Number → Number in bytecode ────────────────
public class NumberBox<T extends Number> {
    private T value;
    public NumberBox(T v) { this.value = v; }
    public T get()        { return value; }   // bytecode: Number get()
}

// At call site with T = Integer:
NumberBox<Integer> box = new NumberBox<>(42);
Integer result = box.get();
// Bytecode generated by compiler (conceptually):
//   Number _temp = box.get();      // return type is Number (the erasure)
//   Integer result = (Integer) _temp;  // compiler inserts checkcast Integer

// ── Multiple bounds — leftmost is the erasure ─────────────────────────
public class SortableNumber<T extends Number & Comparable<T>> {
    private T value;
    public SortableNumber(T v) { this.value = v; }

    // Both bounds in play:
    public double asDouble() { return value.doubleValue(); }  // Number method
    public int compareTo(T other) { return value.compareTo(other); } // Comparable method

    // After erasure, value is of type Number (leftmost bound)
    // Calls to compareTo(other) require a synthetic cast to Comparable in bytecode
    // — the compiler generates this automatically
}

// ── Erasure controls what instanceof can check ────────────────────────
NumberBox<Integer> intBox = new NumberBox<>(42);
NumberBox<Double>  dblBox = new NumberBox<>(3.14);

// Can check against the erased type (Number):
System.out.println(intBox.get() instanceof Number);   // true — Number is the erased type

// Cannot check parameterization at runtime — the JVM doesn't know T:
// if (intBox instanceof NumberBox<Integer>) { }  // COMPILE ERROR

// Can check raw type:
System.out.println(intBox instanceof NumberBox);  // true — raw type check

// ── Why unchecked casts warn ──────────────────────────────────────────
Object obj = List.of(1, 2, 3);

// The JVM can only verify that obj is a List — not that it's List<Integer>:
@SuppressWarnings("unchecked")
List<Integer> checkedList = (List<Integer>) obj;  // unchecked: Integer not verifiable
// At runtime: checks instanceof List only — Integer part is invisible to JVM

// ── Erasure in practice: bridge methods from upper bounds ─────────────
public interface Processor<T extends Serializable> {
    T process(T input);  // erases to: Serializable process(Serializable input)
}

class StringProcessor implements Processor<String> {
    @Override
    public String process(String input) {  // String process(String input)
        return input.toUpperCase();
    }
    // Compiler generates bridge:
    // (synthetic) Serializable process(Serializable input) {
    //     return this.process((String) input);  // delegates with cast
    // }
}

// Polymorphism works correctly through the erased interface:
Processor<String> p = new StringProcessor();
Serializable s = p.process("hello");   // calls bridge → calls real method
System.out.println(s);  // HELLO

// ── Performance: leftmost bound minimizes synthetic casts ─────────────
// Prefer this: Number is primary use, Comparable is secondary:
class OptimizedBox<T extends Number & Comparable<T>> {
    T value;
    void primary()   { value.doubleValue(); }  // direct — Number is erasure
    void secondary() { value.compareTo(value); } // synthetic cast to Comparable in bytecode
}

// Over this: Comparable is accessed directly, Number needs a cast:
class LessOptimized<T extends Comparable<T> & Number> {
    T value;
    void primary()   { value.compareTo(value); }  // direct — Comparable is erasure
    void secondary() { value.doubleValue(); }      // synthetic cast to Number in bytecode
}

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).
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.