AhmadLang / Java, How To Program, 2004
.pdf
to ensure that a reference of the appropriate type is returned, auto-unboxed and assigned to popValue.
Creating Generic Methods to Test Class Stack< E >
Note that the code in methods testPushDouble and testPushInteger is almost identical for pushing values onto a Stack< Double > or a Stack< Integer >, respectively, and the code in methods testPopDouble and testPopInteger is almost identical for popping values from a Stack< Double > or a Stack< Integer >, respectively. This presents another opportunity to use generic methods. Figure 18.11 declares generic method testPush (lines 2646) to perform the same tasks as testPushDouble and testPushInteger in Fig. 18.10that is, push values onto a Stack< T >. Similarly, generic method testPop (lines 4969) performs the same tasks as testPopDouble and testPopInteger in Fig. 18.10that is, pop values off a Stack< T >. Note that the output of Fig. 18.11 precisely matches the output of Fig. 18.10.
Figure 18.11. Passing a generic type Stack to a generic method.
(This item is displayed on pages 887 - 889 in the print version)
1 |
// |
Fig. 18.11: StackTest2.java |
2 |
// |
Stack generic class test program. |
3 |
|
|
4public class StackTest2
5{
6 |
private |
Double[] |
doubleElements |
= |
{ |
1.1, 2.2, 3.3, 4.4, 5.5, 6.6 }; |
7 |
private |
Integer[] |
integerElements |
= |
|
|
8 |
{ 1, |
2, 3, 4, |
5, 6, 7, 8, 9, |
10, |
11 }; |
|
9 |
|
|
|
|
|
|
10private Stack< Double > doubleStack; // stack stores Double objects
11private Stack< Integer > integerStack; // stack stores Integer objects
13// test Stack objects
14public void testStacks()
15 |
{ |
|
|
|
|
16 |
doubleStack = new Stack< Double >( 5 |
); |
// |
Stack of |
Doubles |
17 |
integerStack = new Stack< Integer >( |
10 |
); |
// Stack |
of Integers |
18 |
|
|
|
|
|
19testPush( "doubleStack", doubleStack, doubleElements );
20testPop( "doubleStack", doubleStack );
21testPush( "integerStack", integerStack, integerElements );
22testPop( "integerStack", integerStack );
23} // end method testStacks
24
25 // generic method testPush pushes elements onto a Stack
26 public < T > void testPush( String name, Stack< T > stack,
27T[] elements )
28{
29// push elements onto stack
30try
31{
32System.out.printf( "\nPushing elements onto %s\n", name );
34// push elements onto Stack
35for ( T element : elements )
36{
37 |
System.out.printf( "%s |
", |
element ); |
38 |
stack.push( element ); |
// |
push element onto stack |
39}
40} // end try
41catch ( FullStackException fullStackException )
42{
43System.out.println();
44fullStackException.printStackTrace();
45} // end catch FullStackException
46} // end method testPush
47
48 // generic method testPop pops elements from a Stack
49 public < T > void testPop( String name, Stack< T > stack )
50{
51// pop elements from stack
52try
53{
54System.out.printf( "\nPopping elements from %s\n", name );
55T popValue; // store element removed from stack
56
57// remove elements from Stack
58while ( true )
59{
60 |
popValue = |
stack.pop(); // pop from stack |
61 |
System.out |
.printf( "%s ", popValue ); |
62} // end while
63} // end try
64catch( EmptyStackException emptyStackException )
65{
66System.out.println();
67emptyStackException.printStackTrace();
68} // end catch EmptyStackException
69} // end method testPop
70
71public static void main( String args[] )
72{
73StackTest2 application = new StackTest2();
74application.testStacks();
75} // end main
76} // end class StackTest2
Pushing elements onto doubleStack 1.1 2.2 3.3 4.4 5.5 6.6
FullStackException: Stack is full, cannot push 6.6
|
|
|
|
|
at |
Stack.push(Stack.java:30) |
|||||
|
|
|
|
|
at |
StackTest2.testPush(StackTest2.java:38) |
|||||
|
|
|
|
|
at |
StackTest2.testStacks(StackTest2.java:19) |
|||||
|
|
|
|
|
at |
StackTest2.main(StackTest2.java:74) |
|||||
Popping |
|
elements |
from |
doubleStack |
|||||||
5.5 4.4 |
|
3.3 2.2 1.1 |
|
||||||||
EmptyStackException: Stack is empty, cannot pop |
|||||||||||
|
|
|
|
|
at |
Stack.pop(Stack.java:40) |
|||||
|
|
|
|
|
at |
StackTest2.testPop(StackTest2.java:60) |
|||||
|
|
|
|
|
at |
StackTest2.testStacks(StackTest2.java:20) |
|||||
|
|
|
|
|
at |
StackTest2.main(StackTest2.java:74) |
|||||
Pushing |
|
elements |
onto |
integerStack |
|||||||
1 |
2 |
3 |
4 |
|
5 |
6 |
7 |
8 |
9 |
10 |
11 |
FullStackException: Stack is full, cannot push 11 |
|||||||||||
|
|
|
|
|
at |
Stack.push(Stack.java:30) |
|||||
|
|
|
|
|
at |
StackTest2.testPush(StackTest2.java:38) |
|||||
|
|
|
|
|
at |
StackTest2.testStacks(StackTest2.java:21) |
|||||
|
|
|
|
|
at |
StackTest2.main(StackTest2.java:74) |
|||||
Popping |
|
elements |
from |
integerStack |
|||||||
10 |
|
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
|
EmptyStackException: Stack is empty, cannot pop |
|||||||||||
|
|
|
|
|
at |
Stack.pop(Stack.java:40) |
|||||
|
|
|
|
|
at |
StackTest2.testPop(StackTest2.java:60) |
|||||
|
|
|
|
|
at |
StackTest2.testStacks(StackTest2.java:22) |
|||||
|
|
|
|
|
at |
StackTest2.main(StackTest2.java:74) |
|||||
[Page 889]
The testStacks method (lines 1423) creates the Stack< Double > (line 16) and Stack< Integer > (line 17) objects. Lines 1922 invoke generic methods testPush and testPop to test the Stack objects. Recall that type parameters can represent only reference types. Therefore, to be able to pass arrays doubleElements and integerElements to generic method testPush, the arrays declared in lines 68 must be declared with the wrapper types Double and Integer. When these arrays are initialized with primitive
values, the compiler autoboxes each primitive value.
Generic method testPush (lines 2646) uses type parameter T (specified at line 26) to represent the data type stored in the Stack< T >. The generic method takes three argumentsa String that represents the name of the Stack< T > object for output purposes, a reference to an object of type Stack< T > and an array of type Tthe type of elements that will be pushed onto Stack< T >. Note that the compiler enforces consistency between the type of the Stack and the elements that will be pushed onto the Stack when push is invoked, which is the real value of the generic method call. Generic method testPop (lines 4969) takes two argumentsa String that represents the name of the Stack< T > object for output purposes and a reference to an object of type Stack< T >.
[Page 890]
18.7. Raw Types
The test programs for generic class Stack in Section 18.6 instantiate Stacks with type arguments Double and Integer. It is also possible to instantiate generic class Stack without specifying a type argument, as follows:
Stack objectStack = new Stack( 5 ); // no type argument specified
In this case, the objectStack is said to have a raw type, which means that the compiler implicitly uses type Object throughout the generic class for each type argument. Thus the preceding statement creates a Stack that can store objects of any type. This is important for backwards compatibility with prior versions of Java. For example, the data structures of the Java Collections Framework (see Chapter 19, Collections) all stored references to Objects, but are now implemented as generic types.
A raw type Stack variable can be assigned a Stack that specifies a type argument, such as a Stack< Double > object, as follows:
Stack rawTypeStack2 = new Stack< Double >( 5 );
because type Double is a subclass of Object. This assignment is allowed because the elements in a Stack< Double > (i.e., Double objects) are certainly objectsclass Double is an indirect subclass of
Object.
Similarly, a Stack variable that specifies a type argument in its declaration can be assigned a raw type Stack object, as in:
Stack< Integer > integerStack = new Stack( 10 );
Although this assignment is permitted, it is unsafe because a Stack of raw type might store types other than Integer. In this case, the compiler issues a warning message which indicates the unsafe assignment.
The test program of Fig. 18.12 uses the notion of raw type. Line 14 instantiates generic class Stack with raw type, which indicates that rawTypeStack1 can hold objects of any type. Line 17 assigns a Stack< Double > to variable rawTypeStack2, which is declared as a Stack of raw type. Line 20 assigns a Stack of raw type to Stack< Integer > variable, which is legal but causes the compiler to issue a warning message (Fig. 18.13) indicating a potentially unsafe assignmentagain, this occurs because a Stack of raw type might store types other than Integer. Also, each of the calls to generic method testPush and testPop in lines 2225 results in a compiler warning message (Fig. 18.13). These warnings occur because variables rawTypeStack1 and rawTypeStack2 are declared as Stacks of raw type, but methods testPush and testPop each expect a second argument that is a Stack with a specific type argument. The warnings indicate that the compiler cannot guarantee that the types manipulated by the stacks are the correct types, because we did not supply a variable declared with a type argument. Methods testPush (lines 3151) and testPop (lines 5474) are the same as in Fig. 18.11.
Figure 18.12. Raw type test program.
(This item is displayed on pages 891 - 893 in the print version)
1// Fig. 18.12: RawTypeTest.java
2// Raw type test program.
3
4public class RawTypeTest
5{
6private Double[] doubleElements = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 };
7 |
private |
Integer[] integerElements = |
|
8 |
{ 1, |
2, 3, 4, 5, 6, 7, 8, 9, 10, |
11 }; |
9 |
|
|
|
10// method to test Stacks with raw types
11public void testStacks()
12{
13 |
// Stack of raw types |
assigned to Stack of raw types variable |
14 |
Stack rawTypeStack1 = |
new Stack( 5 ); |
15 |
|
|
16// Stack< Double > assigned to Stack of raw types variable
17Stack rawTypeStack2 = new Stack< Double >( 5 );
18
19// Stack of raw types assigned to Stack< Integer > variable
20Stack< Integer > integerStack = new Stack( 10 );
21
22testPush( "rawTypeStack1", rawTypeStack1, doubleElements );
23testPop( "rawTypeStack1", rawTypeStack1 );
24testPush( "rawTypeStack2", rawTypeStack2, doubleElements );
25testPop( "rawTypeStack2", rawTypeStack2 );
26testPush( "integerStack", integerStack, integerElements );
27testPop( "integerStack", integerStack );
28} // end method testStacks
29
30 // generic method pushes elements onto stack
31 public < T > void testPush( String name, Stack< T > stack,
32T[] elements )
33{
34// push elements onto stack
35try
36{
37System.out.printf( "\nPushing elements onto %s\n", name );
39// push elements onto Stack
40for ( T element : elements )
41{
42 |
System.out.printf( "%s |
", |
element ); |
43 |
stack.push( element ); |
// |
push element onto stack |
44} // end for
45} // end try
46catch ( FullStackException fullStackException )
47{
48System.out.println();
49fullStackException.printStackTrace();
50} // end catch FullStackException
51} // end method testPush
52
53 // generic method testPop pops elements from stack
54 public < T > void testPop( String name, Stack< T > stack )
55{
56// pop elements from stack
57try
58{
59System.out.printf( "\nPopping elements from %s\n", name );
60T popValue; // store element removed from stack
61
62// remove elements from Stack
63while ( true )
64{
65 |
popValue = |
stack.pop(); // pop from stack |
66 |
System.out |
.printf( "%s ", popValue ); |
67} // end while
68} // end try
69catch( EmptyStackException emptyStackException )
70{
71System.out.println();
72emptyStackException.printStackTrace();
73} // end catch EmptyStackException
74} // end method testPop
75
76public static void main( String args[] )
77{
78RawTypeTest application = new RawTypeTest();
79application.testStacks();
80} // end main
81} // end class RawTypeTest
Pushing elements onto rawTypeStack1 1.1 2.2 3.3 4.4 5.5 6.6
FullStackException: Stack is full, cannot push 6.6 at Stack.push(Stack.java:30)
at RawTypeTest.testPush(RawTypeTest.java:43) at RawTypeTest.testStacks(RawTypeTest.java:22) at RawTypeTest.main(RawTypeTest.java:79)
Popping elements from rawTypeStack1 5.5 4.4 3.3 2.2 1.1
EmptyStackException: Stack is empty, cannot pop at Stack.pop(Stack.java:40)
at RawTypeTest.testPop(RawTypeTest.java:65)
at RawTypeTest.testStacks(RawTypeTest.java:23) at RawTypeTest.main(RawTypeTest.java:79)
Pushing elements onto rawTypeStack2 1.1 2.2 3.3 4.4 5.5 6.6
FullStackException: Stack is full, cannot push 6.6 at Stack.push(Stack.java:30)
at RawTypeTest.testPush(RawTypeTest.java:43) at RawTypeTest.testStacks(RawTypeTest.java:24) at RawTypeTest.main(RawTypeTest.java:79)
Popping elements from rawTypeStack2 5.5 4.4 3.3 2.2 1.1
EmptyStackException: Stack is empty, cannot pop at Stack.pop(Stack.java:40)
at RawTypeTest.testPop(RawTypeTest.java:65)
at RawTypeTest.testStacks(RawTypeTest.java:25) at RawTypeTest.main(RawTypeTest.java:79)
Pushing elements onto integerStack 1 2 3 4 5 6 7 8 9 10 11
FullStackException: Stack is full, cannot push 11 at Stack.push(Stack.java:30)
at RawTypeTest.testPush(RawTypeTest.java:43) at RawTypeTest.testStacks(RawTypeTest.java:26) at RawTypeTest.main(RawTypeTest.java:79)
Popping elements from integerStack 10 9 8 7 6 5 4 3 2 1
EmptyStackException: Stack is empty, cannot pop at Stack.pop(Stack.java:40)
at RawTypeTest.testPop(RawTypeTest.java:65)
at RawTypeTest.testStacks(RawTypeTest.java:27) at RawTypeTest.main(RawTypeTest.java:79)
Figure 18.13. Warning message from the compiler.
(This item is displayed on page 894 in the print version)
RawTypeTest.java:20: warning: unchecked assignment found : Stack
required: Stack<java |
.lang.Integer> |
Stack< Integer |
> integerStack = new Stack( 10 ); |
|
^ |
RawTypeTest.java:22: warning: [unchecked] unchecked method invocation: <T>testPush(java.lang.String,Stack<T>,T[]) in RawTypeTest is applied to (java.lang.String,Stack,java.lang.Double[])
testPush( "rawTypeStack1", rawTypeStack1, doubleElements );
^
RawTypeTest.java:23: warning: [unchecked] unchecked method invocation: <T>testPop(java.lang.String,Stack<T>) in RawTypeTest is applied to (java.lang.String,Stack)
testPop( "rawTypeStack1", rawTypeStack1 );
^
RawTypeTest.java:24: warning: [unchecked] unchecked method invocation: <T>testPush(java.lang.String,Stack<T>,T[]) in RawTypeTest is applied to (java.lang.String,Stack,java.lang.Double[])
testPush( "rawTypeStack2", rawTypeStack2, doubleElements );
^
RawTypeTest.java:25: warning: [unchecked] unchecked method invocation: <T>testPop(java.lang.String,Stack<T>) in RawTypeTest is applied to (java.lang.String,Stack)
testPop( "rawTypeStack2", rawTypeStack2 );
^
5 warnings
Figure 18.13 shows the warning messages generated by the compiler (compiled with the - Xlint:unchecked option) when the file RawTypeTest.java (Fig. 18.12) is compiled. The first warning is generated for line 20, which assigned a raw type Stack to a Stack< Integer > variablethe compiler cannot ensure that all objects in the Stack will be Integer objects. The second warning is generated for line 22. Because the second method argument is a raw type Stack variable, the compiler determines the type argument for method testPush from the Double array passed as the third argument. In this case, Double is the type argument, so the compiler expects a Stack< Double > to be passed as the second argument. The warning occurs because the compiler cannot ensure that a raw type Stack contains only Double objects. The warning at line 24 occurs for the same reason, even though the actual Stack that rawTypeStack2 references is a Stack< Double >. The compiler cannot guarantee that the variable will always refer to the same Stack object, so it must use the variable's declared type to perform all type checking. Lines 23 and 25 each generate warnings because method testPop expects as an argument a Stack for which a type argument has been specified. However, in each call to testPop, we pass a raw type Stack variable. Thus, the compiler indicates a warning because it cannot check the types used in the body of the method.
[Page 891]
[Page 891 (continued)]
18.8. Wildcards in Methods That Accept Type Parameters
In this section, we introduce a powerful generics concept known as wildcards. For this purpose, we will also introduce a new data structure from package java.util. Chapter 19, Collections, discusses the Java Collections Framework, which provides many generic data structures and algorithms that manipulate the elements of those data structures. Perhaps the simplest of these data structures is class ArrayLista dynamically resizable, array-like data structure. As part of this discussion, you will learn how to create an ArrayList, add elements to it and traverse those elements using an enhanced for statement.
[Page 893]
Before we introduce wildcards, let's consider an example that helps us motivate their use. Suppose that you would like to implement a generic method sum that totals the numbers in a collection, such as an ArrayList. You would begin by inserting the numbers in the collection. As you know, generic classes can be used only with class or interface types. So the numbers would be autoboxed as objects of the typewrapper classes. For example, any int value would be autoboxed as an Integer object, and any double value would be autoboxed as a Double object. We'd like to be able to total all the numbers in the ArrayList regardless of their type. For this reason, we'll declare the ArrayList with the type argument Number, which is the superclass of both Integer and Double. In addition, method sum will receive a parameter of type ArrayList< Number > and total its elements. Figure 18.14 demonstrates totaling the elements of an ArrayList of Numbers.
[Page 894]
Figure 18.14. Totaling the numbers in an ArrayList< Number >.
(This item is displayed on page 895 in the print version)
1 |
// |
Fig. 18 |
.14: |
TotalNumbers |
.java |
2 |
// |
Summing |
the |
elements of |
an ArrayList. |
3 |
import java.util.ArrayList; |
|
|||
4 |
|
|
|
|
|
5public class TotalNumbers
6{
7public static void main( String args[] )
8{
9// create, initialize and output ArrayList of Numbers containing
10// both Integers and Doubles, then display total of the elements
11 |
Number[] numbers = { 1, 2.4, 3 |
, |
4.1 }; // Integers and |
Doubles |
12 |
ArrayList< Number > numberList |
= |
new ArrayList< Number |
>(); |
13 |
|
|
|
|
14for ( Number element : numbers )
15numberList.add( element ); // place each number in numberList
17System.out.printf( "numberList contains: %s\n", numberList );
18System.out.printf( "Total of the elements in numberList: %.1f\n",
19sum( numberList ) );
20} // end main
21
22// calculate total of ArrayList elements
23public static double sum( ArrayList< Number > list )
24{
25double total = 0; // initialize total
26
27// calculate sum
28for ( Number element : list )
29total += element.doubleValue();
31return total;
32} // end method sum
33} // end class TotalNumbers
numberList contains: [1, 2.4, 3, 4.1] Total of the elements in numberList: 10.5
Line 11 declares and initializes an array of Numbers. Because the initializers are primitive values, Java autoboxes each primitive value as an object of its corresponding wrapper type. The int values 1 and 3 are autoboxed as Integer objects, and the double values 2.4 and 4.1 are autoboxed as Double objects. Line 12 declares and creates an ArrayList object that stores Numbers and assigns it to variable numberList. Note that we do not have to specify the size of the ArrayList because it will grow automatically as we insert objects.
Lines 1415 traverse array numbers and place each element in numberList. Method add of class ArrayList appends an element to the end of the collection. Line 17 outputs the contents of the ArrayList as a String. This statement implicitly invokes the ArrayList's toString method, which returns a string of the form "[ elements ]" in which elements is a comma-separated list of the elements' string representations. Lines 1819 display the sum of the elements that is returned by the call to method sum at line 19.
Method sum (lines 2332) receives an ArrayList of Numbers and calculates the total of the Numbers in the collection. The method uses double values to perform the calculations and returns the result as a double. Line 25 declares local variable total and initializes it to 0. Lines 2829 use the enhanced for statement, which is designed to work with both arrays and the collections of the Collections Framework, to total the elements of the ArrayList. The for statement assigns each Number in the ArrayList to variable element, then uses method doubleValue of class Number to obtain the Number's underlying primitive value as a double value. The result is added to total. When the loop terminates, the method returns the total.
[Page 895]
Implementing Method sum With a Wildcard Type Argument in Its
Parameter
Recall that the purpose of method sum in Fig. 18.14 was to total any type of Numbers stored in an ArrayList. We created an ArrayList of Numbers that contained both Integer and Double objects. The output of Fig. 18.14 demonstrates that method sum worked properly. Given that method sum can total the elements of an ArrayList of Numbers, you might expect that the method would also work for ArrayLists that contain elements of only one numeric type, such as ArrayList< Integer >. So we modified class TotalNumbers to create an ArrayList of Integers and pass it to method sum. When we compile the program, the compiler issues the following error message:
[Page 896]
sum(java.util.ArrayList<java.lang.Number>) in TotalNumbersErrors cannot be applied to (java.util.ArrayList<java.lang.Integer>)
Although Number is the superclass of Integer, the compiler does not consider the parameterized type ArrayList< Number > to be a supertype of ArrayList< Integer >. If it were, then every operation we could perform on ArrayList< Number > would also work on an ArrayList< Integer >. Consider the fact that you can add a Double object to an ArrayList< Number > because a Double is a Number, but you cannot add a Double object to an ArrayList< Integer > because a Double is not an Integer. Thus, the subtype relationship does not hold.
How do we create a more flexible version of method sum that can total the elements of any ArrayList that contains elements of any subclass of Number? This is where wildcard type arguments are important. Wildcards enable you to specify method parameters, return values, variables or fields, etc. that act as supertypes of parameterized types. In Fig. 18.15, method sum's parameter is declared in line 50 with the type:
ArrayList< ? extends Number >
Figure 18.15. Wildcard test program.
(This item is displayed on pages 896 - 897 in the print version)
1// Fig. 18.15: WildcardTest.java
2// Wildcard test program.
3import java.util.ArrayList;
4
5public class WildcardTest
6{
7public static void main( String args[] )
8{
9// create, initialize and output ArrayList of Integers, then
10// display total of the elements
11 |
Integer[] integers |
= |
{ 1, 2, 3, |
4, |
5 }; |
12 |
ArrayList< Integer |
> |
integerList |
= |
new ArrayList< Integer >(); |
13 |
|
|
|
|
|
14// insert elements in integerList
15for ( Integer element : integers )
16integerList.add( element );
17
18System.out.printf( "integerList contains: %s\n", integerList );
19System.out.printf( "Total of the elements in integerList: %.0f\n\n",
20sum( integerList ) );
21
22// create, initialize and output ArrayList of Doubles, then
23// display total of the elements
24Double[] doubles = { 1.1, 3.3, 5.5 };
25ArrayList< Double > doubleList = new ArrayList< Double >();
27// insert elements in doubleList
28for ( Double element : doubles )
29 |
doubleList.add( element ); |
30 |
|
31System.out.printf( "doubleList contains: %s\n", doubleList );
32System.out.printf( "Total of the elements in doubleList: %.1f\n\n",
33sum( doubleList ) );
34
35// create, initialize and output ArrayList of Numbers containing
36// both Integers and Doubles, then display total of the elements
37 |
Number[] numbers = { 1, 2.4, 3 |
, |
4.1 }; // Integers and |
Doubles |
38 |
ArrayList< Number > numberList |
= |
new ArrayList< Number |
>(); |
39 |
|
|
|
|
40// insert elements in numberList
41for ( Number element : numbers )
42numberList.add( element );
43
44System.out.printf( "numberList contains: %s\n", numberList );
45System.out.printf( "Total of the elements in numberList: %.1f\n",
46sum( numberList ) );
47} // end main
48
49// calculate total of stack elements
50public static double sum( ArrayList< ? extends Number > list )
51{
52double total = 0; // initialize total
53
54// calculate sum
55for ( Number element : list )
56total += element.doubleValue();
58return total;
59} // end method sum
60} // end class WildcardTest
