Chapter 5
IN THIS CHAPTER
Discovering why the generics feature was invented
Using generics in your own classes
Working with wildcards in a generic class
Examining a pair of classes that demonstrate generics
In the previous two chapters, you’ve seen how you can specify the type for an ArrayList
or a LinkedList
so the compiler can prevent you from accidentally adding the wrong type of data to the collection. The ArrayList
and LinkedList
classes can do this because they take advantage of a feature called generics. Generics first became available in Java 1.5.
In this chapter, I show you how the generics feature works and how to put it to use in your own classes. Specifically, you see examples of two classes that use the LinkedList
class to implement a specific kind of collection. The first is a stack, a collection in which items are always added to the front of the list and retrieved from the front of the list. The second is a queue, a collection in which items are added to the end of the list and retrieved from the front.
To be sure, I won’t be covering all the intricacies of programming with generics. If your next job happens to be writing Java class libraries for Oracle, you’ll need to know a lot more about generics than this chapter covers. I focus just on the basics of writing simple generic classes.
If you don’t specify otherwise, collection classes can hold any type of object. For example, the add
method for the ArrayList
class had this declaration:
public boolean add(Object o)
{
// code to implement the add method
}
Thus, you can pass any type of object to the add
method — and the array list gladly accepts it.
When you retrieve an item from a collection, you must cast it to the correct object type before you can do anything with it. For example, if you have an array list named empList
with Employee
objects, you’d use a statement like this one to get the first Employee
from the list:
Employee e = (Employee)empList.get(0);
The trouble is, what if the first item in the list isn’t an Employee
? Because the add
method accepts any type of object, there’s no way to guarantee that only certain types of objects could be added to the collection.
That’s where generics come into play. With generics, you can declare the ArrayList
like this:
ArrayList<Employee> empList = new ArrayList<Employee>();
Here empList
is declared as an ArrayList
that can hold only Employee
types. Now the add
method has a declaration that is the equivalent of this:
public boolean add(Employee o)
{
// code to implement the add method
}
Thus you can only add Employee
objects to the list. And the get
method has a declaration that’s equivalent to this:
public Employee get(int index)
{
// code to implement the get method
}
Thus the get
method returns Employee
objects. You don’t have to cast the result to an Employee
because the compiler already knows the object is an Employee
.
Generics let you create classes that can be used for any type specified by the programmer at compile time. To accomplish that, the Java designers introduced a new feature to the language, called formal type parameters. To create a class that uses a formal type parameter, you list the type parameter after the class name in angle brackets. The type parameter has a name — Oracle recommends you use single uppercase letters for type parameter names — that you can then use throughout the class anywhere you’d otherwise use a type.
For example, here’s a simplified version of the class declaration for the ArrayList
class:
public class ArrayList<E>
I left out the extends
and implements
clauses to focus on the formal type parameter: <E>
. The E
parameter specifies the type of the elements that are stored in the list. Oracle recommends the type parameter name E
(for Element) for any parameter that specifies element types in a collection.
So consider this statement:
ArrayList<Employee> empList;
Here the E
parameter is Employee
, which simply means that the element type for this instance of the ArrayList
class is Employee
.
Now take a look at the declaration for the add
method for the ArrayList
class:
public boolean add(E o)
{
// body of method omitted (thank you)
}
Where you normally expect to see a parameter type, you see the letter E
. Thus, this method declaration specifies that the type for the o
parameter is the type specified for the formal type parameter E
. If E
is Employee
, that means the add
method only accepts Employee
objects.
So far, so good. Now take a look at how you can use a formal type parameter as a return type. Here’s the declaration for the get
method:
public E get(int index)
{
// body of method omitted (you're welcome)
}
Here E
is specified as the return type. That means that if E
is Employee
, this method returns Employee
objects.
One final technique you need to know before moving on: You can use the formal type parameter within your class to create objects of any other class that accepts formal type parameters. For example, the clone
method of the ArrayList
class is written like this:
public Object clone()
{
try
{
ArrayList<E> v = (ArrayList<E>) super.clone();
v.elementData = (E[])new Object[size];
System.arraycopy(elementData, 0,
v.elementData, 0, size);
v.modCount = 0;
return v;
}
catch (CloneNotSupportedException e)
{
// this shouldn't happen since we're Cloneable
throw new InternalError();
}
}
You don’t need to look much at the details in this method; just notice that the first statement in the try
block declares an ArrayList
of type <E>
. In other words, the ArrayList
class uses its own formal type parameter to create another array list object of the same type. If you think about it, that makes perfect sense. After all, that’s what the clone
method does: It creates another array list just like this one.
The key benefit of generics is that this typing happens at compile time. Thus, after you specify the value of a formal type parameter, the compiler knows how to do the type checking implied by the parameter. That’s how it knows not to let you add String
objects to an Employee
collection.
Now that you’ve seen the basics of creating generic classes, in this section you look at a simple generic class that implements a stack. A stack is a simple type of collection that lets you add objects to the top of the collection and remove them from the top. I name this Stack
class in this section GenStack
, and it has five methods:
push
: This method adds an object to the top of the stack.pop
: This method retrieves the top item from the stack. The item is removed from the stack in the process. If the stack is empty, this method returns null
.peek
: This method lets you peek at the top item on the stack. In other words, it returns the top item without removing it. If the stack is empty, it returns null
.hasItems
: This method returns a boolean
value of true
if the stack has at least one item in it.size
: This method returns an int
value that indicates how many items are in the stack.The GenStack
class uses a LinkedList
to implement the stack. For the most part, this class simply exposes the various methods of the LinkedList
class using names that are more appropriate for a stack. The complete code for the GenStack
class is shown in Listing 5-1.
LISTING 5-1 The GenStack Class
import java.util.*;
public class GenStack<E>→3
{
private LinkedList<E> list = new LinkedList<E>();→5
public void push(E item)→7
{
list.addFirst(item);
}
public E pop()→12
{
return list.poll();
}
public E peek()→17
{
return list.peek();
}
public boolean hasItems()→22
{
return !list.isEmpty();
}
public int size()→27
{
return list.size();
}
}
The following paragraphs highlight the important details in this class:
<E>
. Thus users of this class can specify the type for the stack's elements.LinkedList
object list to keep the items stored in the stack. The LinkedList
is declared with the same type as the GenStack
class itself. Thus, if the E
type parameter is Employee
, the type for this LinkedList
is Employee
.push
method accepts a parameter of type E
. It uses the linked list’s addFirst
method to add the item to the beginning of the list.pop
method returns a value of type E
. It uses the linked list’s poll
method, which removes and returns the first element in the linked list. If the list is empty, the poll
method — and therefore the pop
method — returns null
.peek
method also returns a value of type E
. It simply returns the result of the linked list’s peek
method.hasItems
method returns the opposite of the linked list’s isEmpty
method.size
method simply returns the result of the linked list’s size
method.That’s all there is to it. The following program gives the GenStack
class a little workout to make sure it functions properly:
public class GenStackTest
{
public static void main(String[] args)
{
GenStack<String> gs = new GenStack<String>();
System.out.println(
"Pushing four items onto the stack.");
gs.push("One");
gs.push("Two");
gs.push("Three");
gs.push("Four");
System.out.println("There are "
+ gs.size() + " items in the stack.\n");
System.out.println("The top item is: "
+gs.peek() + "\n");
System.out.println("There are still "
+ gs.size() + " items in the stack.\n");
System.out.println("Popping everything:");
while (gs.hasItems())
System.out.println(gs.pop());
System.out.println("There are now "
+ gs.size() + " items in the stack.\n");
System.out.println("The top item is: "
+gs.peek() + "\n");
}
}
This program creates a GenStack
object that can hold String
objects. It then pushes four strings onto the stack and prints the number of items in the stack. Next, it uses the peek
method to print the top item and again prints the number of items in the stack, just to make sure the peek
method doesn’t accidentally remove the item. Next, it uses a while
loop to pop each item off the stack and print it. Then, once again, it prints the number of items (which should now be zero), and it peeks at the top item (which should be null
).
Here’s the output that results when you run this program:
Pushing four items onto the stack.
There are 4 items in the stack.
The top item is: Four
There are still 4 items in the stack.
Popping everything:
Four
Three
Two
One
There are now 0 items in the stack.
The top item is: null
Suppose you have a method that’s declared like this:
public void addItems(ArrayList<Object> list)
{
// body of method not shown
}
Thought question: Does the following statement compile?
addItems(new ArrayList<String>());
Answer: Nope.
That’s surprising because String
is a subtype of Object
. So you’d think that a parameter that says it accepts an ArrayList
of objects accepts an ArrayList
of strings.
Unfortunately, inheritance doesn’t work quite that way when it comes to formal type parameters. Instead, you have to use another feature of generics, called wildcards.
In short, if you want to create a method that accepts any type of ArrayList
, you have to code the method like this:
public void addItems(ArrayList<?> list)
In this case, the question mark indicates that you can code any kind of type here.
That’s almost as good as inheritance, but what if you want to actually limit the parameter to collections of a specific superclass? For example, suppose you’re working on a payroll system that has an Employee
superclass with two subclasses named HourlyEmployee
and SalariedEmployee
, and you want this method to accept an ArrayList
of Employee
objects, HourlyEmployee
objects, or SalariedEmployee
objects?
In that case, you can add an extends
clause to the wildcard, like this:
public void addItems(ArrayList<? extends Employee> list)
Then you can call the addItems
method with an ArrayList
of type Employee
, HourlyEmployee
, or SalariedEmployee
.
Alternatively, suppose you want to allow the parameter to accept HourlyEmployee
or its superclass, Employee
. You could code it like this:
public void addItems(ArrayList<? super HourlyEmployee> list)
Here, the parameter can be an HourlyEmployee
or an Employee
. But it can't be a SalariedEmployee
, because SalariedEmployee
is not a superclass of HourlyEmployee
.
Now, before you call it a day, take this example one step further: Suppose this addItems
method appears in a generic class that uses a formal type parameter <E>
to specify the type of elements the class accepts, and you want the addItems
method to accept an ArrayList
of type E
or any of its subclasses. To do that, you'd declare the addItems
method like this:
public void addItems(ArrayList<? extends E> list)
Here the wildcard type parameter <? extends E>
simply means that the ArrayList
can be of type E
or any type that extends E
.
Now that you’ve seen how to use wildcards in a generic class, this section presents a generic class that implements a queue. A queue is another type of collection that lets you add objects to the end of the collection and remove them from the top. Queues are commonly used in all sorts of applications, from data processing applications to sophisticated networking systems.
This queue class is named GenQueue
and has the following methods:
enqueue
: This method adds an object to the end of the queue.dequeue
: This method retrieves the first item from the queue. The item is removed from the queue in the process. If the queue is empty, this method returns null
.hasItems
: This method returns a boolean
value of true
if the queue has at least one item in it.size
: This method returns an int
value that indicates how many items are in the stack.addItems
: This method accepts another GenQueue
object as a parameter. All the items in that queue are added to this queue. In the process, all the items from the queue passed to the method are removed. The GenQueue
parameter must be of the same type as this queue or a subtype of this queue's type.The GenQueue
class uses a LinkedList
to implement its queue. The complete code for the GenQueue
class is shown in Listing 5-2.
LISTING 5-2 The GenQueue Class
import java.util.*;
public class GenQueue<E>→3
{
private LinkedList<E> list = new LinkedList<E>();→5
public void enqueue(E item)→7
{
list.addLast(item);
}
public E dequeue()→11
{
return list.poll();
}
public boolean hasItems()→16
{
return !list.isEmpty();
}
public int size()→21
{
return list.size();
}
public void addItems(GenQueue<? extends E> q)→26
{
while (q.hasItems())
list.addLast(q.dequeue());
}
}
The following paragraphs point out the highlights of this class:
<E>
. Thus, users of this class can specify the type for the elements of the queue.GenStack
class, this class uses a private LinkedList
object list to keep its items.enqueue
method accepts a parameter of type E
. It uses the linked list’s addLast
method to add the item to the end of the queue.dequeue
method returns a value of type E
. Like the pop
method of the GenStack
class, this method uses the linked list’s poll
method to return the first item in the list.hasItems
method returns the opposite of the linked list’s isEmpty
method.size
method returns the result of the linked list’s size
method.addItems
method accepts a parameter that must be another GenQueue
object whose element type is either the same type as this GenQueue
object’s elements or a subtype of this GenQueue
object’s element type. This method uses a while
loop to remove all the items from the q
parameter and add them to this queue.The following program exercises the GenQueue
class:
public class GenQueueTest
{
public static void main(String[] args)
{
GenQueue<Employee> empList;
empList = new GenQueue<Employee>();
GenQueue<HourlyEmployee> hList;
hList = new GenQueue<HourlyEmployee>();
hList.enqueue(new HourlyEmployee(
"Trump", "Donald"));
hList.enqueue(new HourlyEmployee(
"Gates", "Bill"));
hList.enqueue(new HourlyEmployee(
"Forbes", "Steve"));
empList.addItems(hList);
while (empList.hasItems())
{
Employee emp = empList.dequeue();
System.out.println(emp.firstName
+ " " + emp.lastName);
}
}
}
class Employee
{
public String lastName;
public String firstName;
public Employee() {}
public Employee(String last, String first)
{
this.lastName = last;
this.firstName = first;
}
public String toString()
{
return firstName + " " + lastName;
}
}
class HourlyEmployee extends Employee
{
public double hourlyRate;
public HourlyEmployee(String last, String first)
{
super(last, first);
}
}
This program begins by creating a GenQueue
object that can hold Employee
objects. This queue is assigned to a variable named empList
.
Next, the program creates another GenQueue
object. This one can hold HourlyEmployee
objects (HourlyEmployee
is a subclass of Employee
) and is assigned to a variable named hList
.
Then three rookie employees are created and added to the hList
queue. The addItems
method of the empList
queue is then called to transfer these employees from the hList
queue to the empList
queue. Because HourlyEmployee
is a subclass of Employee
, the addItems
method of the empList
queue accepts hList
as a parameter.
Finally, a while
loop is used to print the employees that are now in the empList
queue.
When this program is run, the following is printed on the console:
Donald Trump
Bill Gates
Steve Forbes
Thus the addItems
method successfully transferred the employees from the hlist
queue, which held HourlyEmployee
objects, to the empList
queue, which holds Employee
objects.