AhmadLang / Java, How To Program, 2004
.pdf
used both a firstNode and a lastNode. The lastNode was useful for the insertAtBack and removeFromBack methods of the List class. The insertAtBack method corresponds to the enqueue method of the Queue class.
[Page 868]
Rewrite the List class so that it does not use a lastNode. Thus, any operations on the tail of a list must begin searching the list from the front. Does this affect our implementation of the Queue class (Fig. 17.13)?
17.33(Performance of Binary Tree Sorting and Searching) One problem with the binary tree sort is that the order in which the data is inserted affects the shape of the treefor the same collection of data, different orderings can yield binary trees of dramatically different shapes. The performance of the binary tree sorting and searching algorithms is sensitive to the shape of the binary tree. What shape would a binary tree have if its data were inserted in increasing order? in decreasing order? What shape should the tree have to achieve maximal searching performance?
17.34(Indexed Lists) As presented in the text, linked lists must be searched sequentially. For large lists, this can result in poor performance. A common technique for improving listsearching performance is to create and maintain an index to the list. An index is a set of references to key places in the list. For example, an application that searches a large list of names could improve performance by creating an index with 26 entriesone for each letter of the alphabet. A search operation for a last name beginning with 'Y' would then first search the index to determine where the 'Y' entries begin, then "jump into" the list at that point and search linearly until the desired name is found. This would be much faster than searching the linked list from the beginning. Use the List class of Fig. 17.3 as the basis of an IndexedList class.
Write a program that demonstrates the operation of indexed lists. Be sure to include methods insertInIndexedList, searchIndexedList and deleteFromIndexedList.
17.35In Section 17.7, we created a stack class from class List with inheritance (Fig. 17.10) and with composition (Fig. 17.12). In Section 17.8 we created a queue class from class List with composition (Fig. 17.13). Create a queue class by inheriting from class List. What are the differences between this class and the one we created with composition?
[Page 869]
Chapter 18. Generics
Every man of genius sees the world at a different angle from his fellows.
Havelock Ellis
...our special individuality, as distinguished from our generic humanity.
Oliver Wendell Holmes, Sr.
Born under one law, to another bound.
Lord Brooke
You deal in the raw material of opinion, and, if my convictions have any validity, opinion ultimately governs the world.
Woodrow Wilson
OBJECTIVES
In this chapter you will learn:
To create generic methods that perform identical tasks on arguments of different types.
To create a generic Stack class that can be used to store objects of any class or interface type.
To understand how to overload generic methods with non-generic methods or with other generic methods.
To understand raw types and how they help achieve backwards compatibility.
To use wildcards when precise type information about a parameter is not required in the method body.
The relationship between generics and inheritance.
[Page 870]
Outline
18.1 Introduction
18.2 Motivation for Generic Methods
18.3 Generic Methods: Implementation and Compile-Time Translation
18.4 Additional Compile-Time Translation Issues:
18.5 Overloading Generic Methods
18.6 Generic Classes
18.7 Raw Types
18.8 Wildcards in Methods That Accept Type Parameters
18.9 Generics and Inheritance: Notes
18.10 Wrap-Up
18.11 Internet and Web Resources
Summary
Terminology
Self-Review Exercises
Answers to Self-Review Exercises Exercises
[Page 870 (continued)]
18.1. Introduction
It would be nice if we could write a single sort method that could sort the elements in an Integer array, a String array or an array of any type that supports ordering (i.e., its elements can be compared). It would also be nice if we could write a single Stack class that could be used as a Stack of integers, a Stack of floating-point numbers, a Stack of Strings or a Stack of any other type. It would be even nicer if we could detect type mismatches at compile timeknown as compile-time type safety. For example, if a Stack stores only integers, attempting to push a String on to that Stack should issue a compile-time error.
This chapter discusses one of J2SE 5.0's new featuresgenericswhich provides the means to create the general models mentioned above. Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods or, with a single class declaration, a set of related types, respectively. Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.
We might write a generic method for sorting an array of objects, then invoke the generic method with Integer arrays, Double arrays, String arrays and so on, to sort the array elements. The compiler could perform type checking to ensure that the array passed to the sorting method contains same type elements. We might write a single generic Stack class that manipulates a stack of objects, then instantiate Stack objects for a stack of Integers, a stack of Doubles, a stack of Strings and so on. The compiler could perform type checking to ensure that the Stack stores elements of the same type.
Software Engineering Observation 18.1
Generic methods and classes are among Java's most powerful capabilities for software reuse with compile-time type safety.
This chapter presents generic method and generic class examples. It also considers the relationships between generics and other Java features, such as overloading and inheritance. Chapter 19, Collections, presents an in-depth treatment of the Java Collections Framework's generic methods and classes. A collection is a data structure that maintains references to many objects. The Java Collections Framework uses generics to allow programmers to specify the exact types of objects that a particular collection will store in a program.
Array doubleArray contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7
Array characterArray contains:
H E L L O
[Page 872]
The program begins by declaring and initializing three arrayssix-element Integer array integerArray (line 39), seven-element Double array doubleArray (line 40) and five-element Character array characterArray (line 41). Then, lines 4348 output the arrays.
When the compiler encounters a method call, it always attempts to locate a method declaration that has the same method name and parameters that match the argument types in the method call. In this example, each printArray call exactly matches one of the printArray method declarations. For example, line 44 calls printArray with integerArray as its argument. At compile time, the compiler determines argument integerArray's type (i.e., Integer[]) and attempts to locate a method named printArray that specifies a single Integer[] parameter (lines 714) and sets up a call to that method. Similarly, when the compiler encounters the printArray call at line 46, it determines argument doubleArray's type (i.e., Double[]), then attempts to locate a method named printArray that specifies a single Double[] parameter (lines 1724) and sets up a call to that method. Finally, when the compiler encounters the printArray call at line 48, it determines argument characterArray's type (i.e., Character[]), then attempts to locate a method named printArray that specifies a single Character[] parameter (lines 2734) and sets up a call to that method.
Study each printArray method. Note that the array element type appears in two locations in each methodthe method header (lines 7, 17 and 27) and the for statement header (lines 10, 20 and 30). If we replace the element types in each method with a generic nameby convention we'll use E to represent the "element" typethen all three methods would look like the one in Fig. 18.2. It appears that if we can replace the array element type in each of the three methods with a single generic type, then we should be able to declare one printArray method that can display the string representations of the elements of any array that contains objects. Note that the format specifier %s can be used to output any object's string representationthe object's toString method will be called implicitly. The method in Fig. 18.2 is similar to the generic printArray method declaration we discuss in Section 18.3.
Figure 18.2. printArray method in which actual type names are replaced by convention with the generic name E.
(This item is displayed on page 873 in the print version)
1 public static void printArray( E[] inputArray )
2{
3// display array elements
4 |
for ( E element : inputArray |
) |
5 |
System.out.printf( "%s ", |
element ); |
6 |
|
|
7System.out.println();
8} // end method printArray
[Page 873]
18.3. Generic Methods: Implementation and Compile-Time
Translation
If the operations performed by several overloaded methods are identical for each argument type, the overloaded methods can be more compactly and conveniently coded using a generic method. You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately.
Figure 18.3 reimplements the application of Fig. 18.1 using a generic printArray method (lines 714). Note that the printArray method calls in lines 24, 26 and 28 are identical to those of Fig. 18.1 (lines 44, 46 and 48) and that the outputs of the two applications are identical. This dramatically demonstrates the expressive power of generics.
Figure 18.3. Printing array elements using generic method printArray.
(This item is displayed on page 874 in the print version)
1 |
// |
Fig. 18.3: GenericMethodTest.java |
2 |
// |
Using generic methods to print array of different types. |
3 |
|
|
4public class GenericMethodTest
5{
6// generic method printArray
7 public static < E > void printArray( E[] inputArray )
8{
9// display array elements
10for ( E element : inputArray )
11System.out.printf( "%s ", element );
13System.out.println();
14} // end method printArray
16public static void main( String args[] )
17{
18// create arrays of Integer, Double and Character
19 |
Integer[] intArray = |
{ |
1, |
2 |
, |
3 |
, |
4 |
, |
5 |
}; |
|
|
20 |
Double[] doubleArray |
= |
{ |
1. |
1 |
, 2 |
.2 |
, |
3 |
.3, 4.4, 5.5, |
6.6, 7.7 }; |
||
21 |
Character[] charArray |
= |
{ |
'H', |
|
'E', |
|
'L', 'L', 'O' |
}; |
||||
22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
23System.out.println( "Array integerArray contains:" );
24printArray( integerArray ); // pass an Integer array
25System.out.println( "\nArray doubleArray contains:" );
26printArray( doubleArray ); // pass a Double array
27System.out.println( "\nArray characterArray contains:" );
28printArray( characterArray ); // pass a Character array
29} // end main
30} // end class GenericMethodTest
Array integerArray contains: 1 2 3 4 5 6
Array doubleArray contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7
Array characterArray contains:
H E L L O
Line 7 begins method printArray's declaration. All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( < E > in this example). Each type parameter section contains one or more type parameters (also called formal type parameters), separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type, parameter types and local variable types in a generic method declaration, and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments. A generic method's body is declared like that of any other method. Note that type parameters can represent only reference typesnot primitive types (like int, double and char). Note, too, that the type parameter names throughout the method declaration must match those declared in the type parameter section. For example, line 10 declares element as type E, which matches the type parameter (E) declared in line 7. Also, a type parameter can be declared only once in the type parameter section but can appear more than once in the method's parameter list. For example, the type parameter name E appears twice in the following method's parameter list:
public static < E > void printTwoArrays( E[] array1, E[] array2 )
Type parameter names need not be unique among different generic methods.
Common Programming Error 18.1
When declaring a generic method, failing to place a type parameter section before the return type of a method is a syntax errorthe compiler will not understand the type parameter name when it is encountered in the method.
[Page 874]
Method printArray's type parameter section declares type parameter, E, as the placeholder for the array element type that printArray will output. Note that E appears in the parameter list as the array element type (line 7). The for statement header (line 10) also uses E as the element type. These are the same two locations where the overloaded printArray methods of Fig. 18.1 specified Integer, Double or Character as the array element type. The remainder of printArray is identical to the versions presented in Fig. 18.1.
Good Programming Practice 18.1
It is recommended that type parameters be specified as individual capital letters. Typically, a type parameter that represents the type of an element in an array (or other collection) is named E for "element."
[Page 875]
As in Fig. 18.1, the program begins by declaring and initializing six-element Integer array integerArray (line 19), seven-element Double array doubleArray (line 20) and five-element Character array characterArray (line 21). Then the program outputs each array by calling printArray (lines 24, 26 and 28)once with argument integerArray, once with argument doubleArray and once with argument
characterArray.
When the compiler encounters line 24, it first determines argument integerArray's type (i.e.,
Integer[]) and attempts to locate a method named printArray that specifies a single Integer[] parameter. There is no such method in this example. Next, the compiler determines whether there is a generic method named printArray that specifies a single array parameter and uses a type parameter to represent the array element type. The compiler determines that method printArray (lines 714) is a match and sets up a call to the method. The same process is repeated for the calls to method printArray at lines 26 and 28.
Common Programming Error 18.2
If the compiler cannot match a method call to a non-generic or a generic method declaration, a compilation error occurs.
Common Programming Error 18.3
If the compiler does not find a method declaration that matches a method call exactly, but does find two or more generic methods that can satisfy the method call, a compilation error occurs.
In addition to setting up the method calls, the compiler also determines whether the operations in the method body can be applied to elements of the type stored in the array argument. The only operation performed on the array elements in this example is to output the string representation of the elements. Line 11 performs an implicit toString call on every element. To work with generics, every element of the array must be an object of a class or interface type. Since all objects have a toString method, the compiler is satisfied that line 11 performs a valid operation for any object in printArray's array argument. The toString methods of classes Integer, Double and Character return the string representation of the underlying int, double or char value, respectively.
When the compiler translates generic method printArray into Java bytecodes, it removes the type parameter section and replaces the type parameters with actual types. This process is known as erasure. By default all generic types are replaced with type Object. So the compiled version of method printArray appears as shown in Fig. 18.4there is only one copy of this code that is used for all printArray calls in the example. This is quite different from other, similar mechanisms, such as C++'s templates in which a separate copy of the source code is generated and compiled for every type passed as an argument to the method. As we will discuss in Section 18.4, the translation and compilation of generics is a bit more involved than what we have discussed in this section.
Figure 18.4. Generic method printArray after erasure is performed by the compiler.
(This item is displayed on page 876 in the print version)
1 public static void printArray( Object[] inputArray )
2{
3// display array elements
4 |
for ( Object element : inputArray ) |
5 |
System.out.printf( "%s ", element ); |
6 |
|
7System.out.println();
8} // end method printArray
By declaring printArray as a generic method in Fig. 18.3, we eliminated the need for the overloaded methods of Fig. 18.1, saving 20 lines of code and creating a reusable method that can output the string representations of the elements in any array that contains objects. However, this particular example could have simply declared the printArray method as shown in Fig. 18.4 using an Object array as the parameter. This would have yielded the same results because any Object can be output as a String. In a generic method, the benefits become apparent when the method also uses a type parameter as the method's return type, as we demonstrate in the next section.
