Replace the specific type String with a type parameter such as T, and we add the type parameter to the name
of the class:
class
Queue<T> {
private LinkedList<T> items = new
LinkedList<T>();
public void enqueue(T item) {
items.addLast(item);
}
public T dequeue() {
return items.removeFirst();
}
public boolean isEmpty() {
return (items.size() == 0);
}
}
the type parameter T is
used just like any regular type name. It's used to declare the return type for dequeue,
as the type of the formal parameter item in enqueue,
and even as the actual type parameter in LinkedList<T>.
Given this class definition, we can use parameterized types such as Queue<String> and Queue<Integer> and Queue<JButton>.
That is, the Queue class
is used in exactly the same way as built-in generic classes like LinkedList and HashSet.
Note that you don't have to
use "T" as the name of the type parameter in the definition of the
generic class. Type parameters are like formal parameters in subroutines. You
can make up any name you like in the definition of
the class. The name in the definition will be replaced by an actual type name
when the class is used to declare variables or create objects. If you prefer to
use a more meaningful name for the type parameter, you might define the Queue class
as:
class Queue<ItemType> {
private
LinkedList<ItemType>
items = new LinkedList<ItemType>();
public void
enqueue(ItemType
item) {
items.addLast(item);
}
public ItemType dequeue() {
return
items.removeFirst();
}
public boolean
isEmpty() {
return
(items.size() == 0);
}
}
Changing the name from "T"
to "ItemType" has absolutely no effect on the
meaning of the class definition or on the way that Queue is
used.
Generic interfaces can be
defined in a similar way. It's also easy to define generic classes and
interfaces that have two or more type parameters, as is done with the standard
interface Map<T,S>. A
typical example is the definition of a "Pair" that contains two
objects, possibly of different types. A simple version of such a class can be
defined as:
class Pair<T,S> {
public T first;
public S second;
public Pair( T
a, S b ) { // Constructor.
first = a;
second = b;
}
}
This class can be used to
declare variables and create objects such as:
Pair<String,Color> colorName = new
Pair<String,Color>("Red", Color.RED);
Pair<Double,Double> coordinates = new
Pair<Double,Double>(17.3,42.8);
Note that in the definition of
the constructor in this class, the name "Pair"
does not have
type parameters. You might have expected "Pair<T,S>.
However, the name of the class is "Pair",
not "Pair<T,S>, and within the definition of the
class, "T" and "S"
are used as if they are the names of specific, actual types.
Note in any case
that type parameters are never added
to the names of methods or constructors, only to the names of classes and
interfaces.
We need to replace the specific type String in
the definition of the method with the name of a type parameter, such as T.
However, if that's the only change we make, the compiler will think that
"T" is the name of an actual type, and it will mark it as an
undeclared identifier. We need some way of telling the compiler that
"T" is a type parameter. That's what the "<T>"
does in the definition of the generic class "class Queue<T> { ...".
For a generic method, the "<T>" goes just
before the name of the return type of the method:
public static <T> int countOccurrences(T[] list, T
itemToCount) {
int count = 0;
if (itemToCount
== null) {
for ( T
listItem : list )
if
(listItem == null)
count++;
}
else {
for ( T
listItem : list )
if
(itemToCount.equals(listItem))
count++;
}
return count;
}
The "<T>"
marks the method as being generic and specifies the name of the type parameter
that will be used in the definition. Of course, the name of the type parameter
doesn't have to be "T"; it can be anything. (The "<T>"
looks a little strange in that position, I know, but it had to go somewhere and
that's just where the designers of Java decided to put it.)
The countOccurrences method
operates on an array. We could also write a similar method to count occurrences
of an object in any collection:
public static <T> int
countOccurrences(Collection<T> collection, T itemToCount) {
int count = 0;
if (itemToCount
== null) {
for ( T item
: collection )
if (item
== null)
count++;
}
else {
for ( T item
: collection )
if (itemToCount.equals(item))
count++;
}
return count;
}
Since Collection<T> is
itself a generic type, this method is very general. It can operate on an ArrayList of Integers,
a TreeSet of Strings,
a LinkedList of JButtons, ....
Type Wildcard
Let's start with a simple example in which a wildcard
type is useful. Suppose that Shape is
a class that defines a method public void
draw(), and suppose that Shape has
subclasses such asRect and Oval.
Suppose that we want a method that can draw all the shapes in a collection of Shapes.
We might try:
public static void drawAll(Collection<Shape>
shapes) {
for ( Shape s :
shapes )
s.draw();
}
This method works fine if we
apply it to a variable of type Collection<Shape>,
or ArrayList<Shape>,
or any other collection class with type parameter Shape.
Suppose, however, that you have a list of Rects stored
in a variable named rectangles of
type Collection<Rect>.
Since Rects are Shapes,
you might expect to be able to call drawAll(rectangles).
Unfortunately, this will not work; a collection of Rects is not considered
to be a collection of Shapes! The variable rectangles cannot
be assigned to the formal parameter shapes.
The solution is to replace the type parameter "Shape"
in the declaration of shapes with
the wildcard type
"? extends Shape":
public static void drawAll(Collection<? extends Shape>
shapes) {
for ( Shape s :
shapes )
s.draw();
}
The wildcard type "? extends Shape"
means roughly "any type that is either equal to Shape or
that is a subclass of Shape". When the
parameter shapes is
declared to be of typeCollection<? extends Shape>,
it becomes possible to call the drawAll method
with an actual parameter of type Collection<Rect> since Rect is
a subclass of Shape and
therefore matches the wildcard. We could also pass actual parameters to drawAll of
type ArrayList<Rect> or Set<Oval> or List<Oval>.
And we can still pass variables of type Collection<Shape> orArrayList<Shape>,
since the class Shape itself
matches "? extends Shape". We have greatly
increased the usefulness of the method by using the wildcard type.
One final remark: The wildcard type "<?>"
is equivalent to "<? extends Object>".
That is, it matches any possible type. For example, the removeAll() method
in the generic interfaceCollections<T> is
declared as
public boolean removeAll( Collection<?> c ) { ...
This just means that the removeAll method
can be applied to any collection of any type of object.