In this previous tutorials we gone through wildcard bounds. Similarly, we can set bounds for type parameters, and this tutorial explains its use.

This tutorial uses following class hierarchy:

Java Generics Type Parameter Bound

Type Parameter Bound

The List<E> can take any type as type argument. With Type Parameter Bound we can restrict accepted types.


    public class PetList<T extends Pet> {

        public void add(T element) {
        }

        public T get(int index) {
            // return element
        }
    }

    PetList<Pet> pets = new PetList<>();
    PetList<Bulldog> bulldogs = new PetList<>();
    PetList<Persian> persians = new PetList<>();

    // Error: String is not subtype of Pet
    
    PetList<String> strings = new PetList<>();     // error

The PetList accepts Pet or its subtypes as T extends Pet, but any other type is not accepted.

 
 

Member Visibility

Let’s look at the member visibility of unbounded type.


    public class List<T> {
        
        public void someMethod(T element){
        
            /*
            * for the parameter element, we can access the members (fields and methods)
            * of Object class such as getClass() etc., as compiler doesn't what object
            * will be passed during runtime.
            */
            
            element.getClass();
        }
    }

The actual type that replaces T can be anything and only thing that is known to compiler is that it is subtype of Java Object class. Because of this, we can only access methods such as getClass(), hashCode() and equals() etc., of Object class on the parameter element.

Next, let’s see how member visibility changes with bounded types,


    public class DogList<T extends Dog> {
    
        public void someMethod(T element){            
             
             /*
             * we can access members of Dog and its supertype - Pet and Object.             
             */
             
             element.aDogMethod();
             element.aPetMethod();             
             element.getClass();

             element.aBulldogMethod(); // error
        }
        
    }

When T is extended with with Dog the compiler treats T as Dog. All the methods of Dog and its super types, Pet and Object, are visible.

Twister

We have to keep two aspects in mind while working with type parameter bounds.

  • within the DogList the members of Dog and its super types are visible i.e. members of Dog, Pet and Object are visible but not Bulldog.
  • we can create DogList of Dog or Bulldog but not Pet as <T extends Dog> allows only Dog and its subtypes.

    // members of Dog and its super types are visible
    public class DogList<T extends Dog> {    
        public void someMethod(T element){
             element.aDogMethod();
             element.aPetMethod();             
             element.getClass();

             element.aBulldogMethod(); // error
        }        
    }

    // can create lists of Dog and its sub types
    DogList<Dog> dogs = new DogList<>();
    DogList<Bulldog> bulldogs = new DogList<>();

    DogList<Pet> pets = new DogList<>();  // error
 
 

Recursive Type Bound

A type parameter bounded by an expression may contain type parameter itself. This is known as recursive type bound. The max() method from javax.util.Collection which used recursive type bound is shown below:


   public static <T extends Comparable<T>> T max(List<T> list) { ... }

The type parameter of max() is <T extends Comparable<T>> which is read as “for every type T that can be compared to itself”. In other words, every element of list should be comparable with each other.

Summary

  • the <T extends type> restricts the generic type to a type and its subtypes.
  • type parameter bound also make members of type and all its super type visible within the generic type.
  • there is no type parameter bound <T super type>.
  • recursive type bound <T extends Comparable<T>> is read as “for every type T that can be compared to itself”.