Introduction

In previous articles, we have mentioned that 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 when 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 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 generic classes. Trying to extend a generic class (for example, Storage<Brochures> extends Storage<Books>) will lead to an error.

Principles

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

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,

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. You will learn about wildcards in the next article.

Leave a Reply

Your email address will not be published.