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.