Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Multiple bounds in Java Generics restrict a type parameter to subtypes that satisfy a combination of multiple types. By using an intersection type, this mechanism ensures that the generic type parameter inherits the method signatures and contracts of all specified bounds simultaneously. In generic bound syntax, the extends keyword is used to denote both class inheritance and interface implementation. Multiple bounds are separated by the ampersand (&) operator.
<T extends Bound1 & Bound2 & Bound3>

Structural Rules and Constraints

When defining multiple bounds, the Java compiler enforces strict structural rules due to Java’s single-inheritance model and generic type system constraints:
  1. Single Class Limitation: A type parameter can be bounded by at most one concrete or abstract class.
  2. Strict Ordering: If a class is specified in the bound list, it must be the first bound declared. Interfaces must follow the class.
  3. Unlimited Interfaces: A type parameter can be bounded by any number of interfaces.
  4. Wildcard Limitation: Multiple bounds cannot be used with wildcards. They are strictly limited to type parameter declarations (e.g., List<? extends Runnable & Serializable> is invalid syntax).
  5. Type Variable Limitation: A type parameter cannot have multiple bounds if one of the bounds is another type variable (e.g., <T extends U & Runnable> results in a compilation error).
  6. Final Class Limitation: If the first bound is a final class, no additional bounds are permitted. A final class cannot be further extended to implement additional interfaces, rendering any intersection impossible.

Syntax Visualization

The following examples demonstrate valid and invalid declarations based on the compiler’s structural rules:
// VALID: Class is declared first, followed by interfaces.
class ValidEntity<T extends Number & Comparable<T> & Serializable> {
    private T data;
}

// VALID: Multiple interfaces with no class bound.
class InterfaceEntity<T extends Runnable & Cloneable> {
    private T task;
}

// INVALID: A class (Thread) is specified, but it is not the first bound.
// Compilation Error: The type Thread is not an interface; it cannot be specified as a bounded parameter.
class InvalidOrderEntity<T extends Runnable & Thread> { }

// INVALID: Attempting to bind to multiple classes.
// Compilation Error: A type parameter cannot extend multiple classes.
class InvalidClassEntity<T extends Thread & Number> { }

// INVALID: Using a type variable as part of a multiple bound.
// Compilation Error: A type variable may not be followed by other bounds.
class InvalidTypeVarEntity<U, T extends U & Runnable> { }

// INVALID: The first bound is a final class.
// Compilation Error: A type parameter cannot have multiple bounds if the first bound is a final class.
class InvalidFinalEntity<T extends String & Runnable> { }

Type Erasure Mechanics

During compilation, Java applies type erasure to generic parameters. When multiple bounds are present, the compiler replaces the generic type parameter T with the first bound listed in the declaration.
class ErasureDemonstration<T extends Number & Runnable> {
    T payload;
    
    public void execute() {
        payload.run();
    }
}
After type erasure, the compiled bytecode translates the class structure to use the first bound (Number). To maintain type safety when invoking methods belonging to subsequent bounds (like Runnable.run()), the compiler automatically inserts the necessary type casts:
// Equivalent bytecode representation after type erasure
class ErasureDemonstration {
    Number payload; // Replaced with the first bound
    
    public void execute() {
        ((Runnable) payload).run(); // Compiler-inserted cast for secondary bounds
    }
}
Because the first bound dictates the erased type, ordering impacts the generated bytecode. Placing a class first (which is mandatory if a class is used) ensures the erased type is a class rather than an interface, optimizing method dispatch at runtime.
Master Java with Deep Grasping Methodology!Learn More