Learn Java

Java Type Bounds

As you know, generics can accept any type of parameter and make it possible to reuse some code. Let's now consider an example that will reveal another aspect of generics. Imagine that we have a generic Storage<T> class that can contain objects of any class. But there are some situations in which we want to restrict these objects. We can say, for example, that the storage has to be able to contain only books. In these types of situations, we should use type bounds.

Usage

Let us take a closer look at type bounds. Consider this code:

class Storage<T> {
    private T nameOfElements;
    //other methods
}

We can put any type of object inside Storage<T>. As stated earlier, we would like to limit this class to be able to store only books. Let's assume we have a Books class to represent all books. Then we can implement our limitation by adding <T extends Books>:

class Storage<T extends Books> {
    private T nameOfElements;
    //other methods
}

Let us create three classes:

public class Books { }
public class Brochures extends Books { }
public class Computers { }

Now, creating three Storage objects will lead to different results:

Storage<Books> storage1 = new Storage<>(); //no problem
Storage<Brochures> storage2 = new Storage<>(); //no problem
Storage<Computers> storage3 = new Storage<>(); //a compile-time error

The first two lines will compile without problems. The third one, however, will return an compile error: Type parameter 'Computers' is not within its bound; should extend Books. Since this is a compile-time error, we catch this problem before it can appear in a real application. For this reason, type bounds are safe to use.

Note that extends can mean not only an extension of a certain class but also an implementation of an interface. Generally speaking, this word is used as a replacement for an extension of normal classes, not classes with generics type. Trying to extend a generic class (for example, Storage<Brochures> extends Storage<Books>) will lead to an runtime error.

Principles

Type bounding involves two keywords: extends keyword and super keyword, each with their own rules regulating their utilization. In this topic, however, we deal with the most common use of bound type: setting an upper bound with the extends keyword. We will learn more about the principles underlying each keyword with Wildcards.

Note that under the hood, every type variable declared as a type parameter has a bound. If no bound is declared, Object is the bound. For this reason, the following code snippet:

class SomeClass<T> {...}

is equivalent to

class SomeClass<T extends Object> {...}

Multiple bounds

A type variable may have a single type bound:

<T extends A>

or have multiple bounds:

<T extends A & B & C & ...>

The first type bound A can be a class or an interface. The rest of the type bounds (B onwards) must be interfaces.

Note: if T has a bound that is a class, this class must be specified first! Otherwise, a compile-time error arises:

<T extends B & C & A & ...> //an error if A is a class

Conclusion

Type bounds are widely used to restrict type parameters. The most common use of type bounds is to set upper bounds with the extends keyword. Certain situations, however, require the use of wildcards, a topic closely related to type bounds.

Create a free account to access the full topic

“It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.”
Andrei Maftei
Hyperskill Graduate