In the process of discussing generics, we’ve already had to
mention bounds a few times. A bound is a constraint on the type of a type
parameter. Bounds use the extends
keyword and some
new syntax to limit the parameter types that may be applied to a generic
type. In the case of a generic class, the bounds simply limit the type
that may be supplied to instantiate it.
A type variable may extend a class or interface type, meaning that its instantiation must be of that type or a subtype:
class
EmployeeList
<
T
extends
Employee
>
{
...
}
Here, we made a generic EmployeeList
type that can be instantiated only
with Employee
types. We could further
require that the Employee
type
implement one or more interfaces using the special &
syntax:
class
EmployeeList
<
T
extends
Employee
&
Ranked
&
Printable
>
{
...
}
The order of the &
interface
bounds is not significant, but only one class type can be specified and if
there is one, it must come first. When a type has no specific bounds, the
bound extends Object
is
implicit.
By applying bounds to our type, we not only limit the instantiations
of the generic class, but we make the type arguments more useful. Now that
we know that our type must extend some type or implement some set of
interfaces, we can use variables and arguments declared with T
by those other type names. Here is a somewhat
contrived extension of our previous example:
class
EmployeeList
<
T
extends
Employee
&
Ranked
&
Printable
>
{
Ranked
ranking
;
List
<
Printable
>
printList
=
new
ArrayList
<
Printable
>();
public
void
addEmployee
(
T
employee
)
{
this
.
ranking
=
employee
;
// T as Ranked
printList
.
add
(
employee
);
// T as Printable
}
}
This example shows that by placing bounds on the generic parameter
type we can require it to be of a particular class type or implement
certain interface types. This allows us to use arguments of the parameter
type passed to methods in more useful ways. In this example, we know that
the EmployeeList
will be instantiated
with a generic type that is a Printable
and so we can use the employee argument as a Printable
.
Type variables can also refer to other type variables within the type declaration:
class
Foo
<
A
,
B
extends
A
>
{
...
}
We’ll see a particularly vicious example of this later when we talk
about the definition of the Enum
class.
We’ll also see a more convenient technique for declaring how individual
elements of a generic class relate to the parameter type when we cover
wildcards in the next section.
We mentioned earlier in our discussion of erasure that
the resulting type used in place of the type parameter in the raw type
for the generic class is the bound of the type variable. Specifically,
we have seen many generics with no explicit bounds that defaulted to a
bound of type Object
. We also showed
a quick example of a type that imposed a bound of extends Date
and said that the type of its
methods would be Date
instead of
Object
. We can now be a little more
specific.
The type after erasure used for the parameter type of a
generic class is the leftmost bound; that is, the
first bound specified after the extends
keyword (literally the leftmost)
becomes the type used in the erasure. This implies that if the type
extends a class type, it is always the erased type because it must
always come first. But if the type extends only interface types, the
choice is up to us. This fine point is important for backward
compatibility with nongeneric code. Often when creating generic versions
of nongeneric APIs, we have the opportunity to “tighten up” the
specification a bit. Being aware of the leftmost bound gives us a way to
explicitly control the type of the erased class. For example, suppose we
create a generic List
class that we
only want instantiated on Listable
objects, but we’d prefer not to change the API of our old List
class that accepted Object
type elements. Our initial
attempt:
class
List
<
E
extends
Listable
>
{
...
}
produces a raw type that accepts only Listable
. However, we can insert a somewhat
gratuitous additional type, Object
,
as the leftmost bound in order to get back our old API without changing
the new generic bounds:
class
List
<
E
extends
Object
&
Listable
>
{
...
}
Inserting Object
doesn’t change
the actual bounds of the generic class but does change the erased
signature.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.