Generics are an enhancement to the syntax of classes that allow us to specialize the class for a given type or set of types. A generic class requires one or more type parameters wherever we refer to the class type and uses them to customize itself.
If you look at the source or Javadoc for the List
class, for example, you’ll see it defined
something like this:
public
class
List
<
E
>
{
...
public
void
add
(
E
element
)
{
...
}
public
E
get
(
int
i
)
{
...
}
}
The identifier E
between the angle
brackets (<>
) is a type
variable. It indicates that the class List
is generic and requires a Java type as an
argument to make it complete. The name E
is arbitrary, but there are conventions that
we’ll see as we go on. In this case, the type variable E
represents the type of elements we want to
store in the list. The List
class
refers to the type variable within its body and methods as if it were a
real type, to be substituted later. The type variable may be used to
declare instance variables, arguments to methods, and the return type of
methods. In this case, E
is used as the
type for the elements we’ll be adding via the add()
method and the return type of the get()
method. Let’s see how to use it.
The same angle bracket syntax supplies the type parameter when we
want to use the List
type:
List
<
String
>
listOfStrings
;
In this snippet, we declared a variable called listOfStrings
using the generic type List
with a type parameter of String
. String
refers to the String
class, but we could have specialized
List
with any Java class type. For
example:
List
<
Date
>
dates
;
List
<
java
.
math
.
BigDecimal
>
decimals
;
List
<
Foo
>
foos
;
Completing the type by supplying its type parameter is
called instantiating the type. It is also sometimes
called invoking the type, by analogy with invoking a
method and supplying its arguments. Whereas with a regular Java type, we
simply refer to the type by name, a generic type must be instantiated with
parameters wherever it is used.[21] Specifically, this means that we must instantiate the type
everywhere types can appear as the declared type of a variable (as shown
in this code snippet), as the type of a method argument, as the return
type of a method, or in an object allocation expression using the
new
keyword.
Returning to our listOfStrings
,
what we have now is effectively a List
in which the type String
has been
substituted for the type variable E
in
the class body:
public
class
List
<
String
>
{
...
public
void
add
(
String
element
)
{
...
}
public
String
get
(
int
i
)
{
...
}
}
We have specialized the List
class to work with elements of type String
and only elements of
type String
. This method signature is
no longer capable of accepting an arbitrary Object
type.
List
is just an interface. To use
the variable, we’ll need to create an instance of some actual
implementation of List
. As we did in
our introduction, we’ll use ArrayList
.
As before, ArrayList
is a class that
implements the List
interface, but in
this case, both List
and ArrayList
are generic classes. As such, they
require type parameters to instantiate them where they are used. Of
course, we’ll create our ArrayList
to
hold String
elements to match our
List
of String
s:
List
<
String
>
listOfStrings
=
new
ArrayList
<
String
>
List
<
String
>
listOfStrings
=
new
ArrayList
<>();
// Or shorthand in Java 7.0
// and later
As always, the new
keyword takes a Java
type and parentheses with possible arguments for the class’s constructor.
In this case, the type is ArrayList<String>
—the generic ArrayList
type instantiated with the String
type.
Declaring variables as shown in the first line of the
preceding example is a bit cumbersome because it requires us to type the
generic parameter type twice (once on the left side in the variable type
and once on the right in the initialing expression). And in complicated
cases, the generic types can get very lengthy and nested within one
another. In Java 7, the compiler is smart enough to infer the type of the
initializing expression from the type of the variable to which you are
assigning it. This is called generic type inference
and boils down to the fact that you can shorthand the right side of your
variable declarations by leaving out the contents of the <>
notation, as shown in the example’s
second line.
We can now use our specialized List
with strings. The compiler prevents us from
even trying to put anything other than a String
object (or a subtype of String
if there were any) into the list and
allows us to fetch them with the get()
method without requiring any cast:
List
<
String
>
listOfStrings
=
new
ArrayList
<
String
>();
listOfStrings
.
add
(
"eureka! "
);
String
s
=
listOfStrings
.
get
(
0
);
// "eureka! "
listOfStrings
.
add
(
new
Date
()
);
// Compile-time Error!
Let’s take another example from the Collections API. The Map
interface provides a
dictionary-like mapping that associates key objects with value objects.
Keys and values do not have to be of the same type. The generic Map
interface requires two type parameters: one
for the key type and one for the value type. The Javadoc looks like
this:
public
class
Map
<
K
,
V
>
{
...
public
V
put
(
K
key
,
V
value
)
{
...
}
// returns any old value
public
V
get
(
K
key
)
{
...
}
}
We can make a Map
that stores
Employee
objects by Integer
“employee ID” numbers like this:
Map
<
Integer
,
Employee
>
employees
=
new
HashMap
<
Integer
,
Employee
>();
Integer
bobsId
=
...;
Employee
bob
=
...;
employees
.
put
(
bobsId
,
bob
);
Employee
employee
=
employees
.
get
(
bobsId
);
Here, we used HashMap
, which is a
generic class that implements the Map
interface, and
instantiated both types with the type parameters Integer
and Employee
. The Map
now works only with keys of type Integer
and holds values of type Employee
.
The reason we used Integer
here
to hold our number is that the type parameters to a generic class must be
class types. We can’t parameterize a generic class with a primitive type,
such as int
or boolean
. Fortunately, autoboxing of primitives
in Java (see Chapter 5) makes it almost appear
as if we can by allowing us to use primitive types as though they were
wrapper types:
employees
.
put
(
42
,
bob
);
Employee
bob
=
employees
.
get
(
42
);
Here, autoboxing converted the integer 42
to an Integer
wrapper for us twice.
In Chapter 11, we’ll see that all of the Java collection classes and interfaces are generic. Furthermore, dozens of other APIs use generics to let you adapt them to specific types. We’ll talk about them as they occur throughout the book.
Before we move on to more important things, we should say
a few words about the way we describe a particular parameterization of a
generic class. Because the most common and compelling case for generics
is for container-like objects, it’s common to think in terms of a
generic type “holding” a parameter type. In our example, we called our
List<String>
a “list of
strings” because, sure enough, that’s what it was. Similarly, we might
have called our employee map a “Map of employee IDs to Employee
objects.” However, these descriptions focus a little more on what the
classes do than on the type itself. Take instead a
single object container called Trap< E
>
that could be instantiated on an object of type Mouse
or of type Bear
; that is, Trap<Mouse>
or Trap<Bear>
. Our instinct is to call the
new type a “mouse trap” or “bear trap.” Similarly, we could have thought
of our list of strings as a new type: “string list” or our employee map
as a new “integer employee object map” type. You may use whatever
verbiage you prefer, but these latter descriptions focus more on the
notion of the generic as a type and may help a
little bit later when we discuss how generic types are related in the
type system. There we’ll see that the container terminology turns out to
be a little counterintuitive.
In the following section, we’ll continue our discussion of generic types in Java from a different perspective. We’ve seen a little of what they can do; now we need to talk about how they do it.
[21] That is, unless you want to use a generic type in a nongeneric way. We’ll talk about “raw” types later in this chapter.
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.