7. Arrays

Objectives

In this chapter you’ll learn:

• To use arrays to store data in and retrieve data from lists and tables of values.

• To declare arrays, initialize arrays and refer to individual elements of arrays.

• To use the enhanced for statement to iterate through arrays.

• To pass arrays to methods.

• To declare and manipulate multidimensional arrays.

• To write methods that use variable-length argument lists.

• To read command-line arguments into a program.

Now go, write it before them in a table, and note it in a book.

Isaiah 30:8

To go beyond is as wrong as to fall short.

Confucius

Begin at the beginning, ... and go on till you come to the end: then stop.

Lewis Carroll

Outline

7.1   Introduction

7.2   Arrays

7.3   Declaring and Creating Arrays

7.4   Examples Using Arrays

7.5   Case Study: Card Shuffling and Dealing Simulation

7.6   Enhanced for Statement

7.7   Passing Arrays to Methods

7.8   Case Study: Class GradeBook Using an Array to Store Grades

7.9   Multidimensional Arrays

7.10   Case Study: Class GradeBook Using a Two-Dimensional Array

7.11   Variable-Length Argument Lists

7.12   Using Command-Line Arguments

7.13   (Optional) Software Engineering Case Study: Collaboration Among Objects

7.14   Wrap-Up

7.1 Introduction

This chapter introduces data structures—collections of related data items. Arrays are data structures consisting of related data items of the same type. Arrays are fixed-length entities—they remain the same length once they are created, although an array variable may be reassigned such that it refers to a new array of a different length. We study several of the Java API’s built-in data structures in Chapter 16.

After discussing how arrays are declared, created and initialized, we present a series of practical examples that demonstrate several common array manipulations. We also present a case study that examines how arrays can help simulate the shuffling and dealing of playing cards for use in an application that implements a card game. The chapter then introduces Java’s enhanced for statement, which allows a program to access the data in an array more easily than does the counter-controlled for statement presented in Section 5.3. Two sections of the chapter enhance the case study of class GradeBook in Chapters 35. In particular, we use arrays to enable the class to maintain a set of grades in memory and analyze student grades from multiple exams in a semester—two capabilities that were absent from previous versions of the class. These and other chapter examples demonstrate the ways in which arrays allow programmers to organize and manipulate data.

7.2 Arrays

An array is a group of variables (called elements or components) containing values that all have the same type. Recall that types are divided into two categories—primitive types and reference types. Arrays are objects, so they are considered reference types. As you’ll soon see, what we typically think of as an array is actually a reference to an array object in memory. The elements of an array can be either primitive types or reference types (including arrays, as we’ll see in Section 7.9). To refer to a particular element in an array, we specify the name of the reference to the array and the position number of the element in the array. The position number of the element is called the element’s index or subscript.

Figure 7.1 shows a logical representation of an integer array called c. This array contains 12 elements. A program refers to any one of these elements with an array-access expression that includes the name of the array followed by the index of the particular element in square brackets ([]). The first element in every array has index zero and is sometimes called the zeroth element. Thus, the elements of array c are c[0], c[1], c[2] and so on. The highest index in array c is 11, which is 1 less than 12—the number of elements in the array. Array names follow the same conventions as other variable names.

Fig. 7.1. A 12-element array.

Image

An index must be a nonnegative integer. A program can use an expression as an index. For example, if we assume that variable a is 5 and variable b is 6, then the statement

c[ a + b ] += 2;

adds 2 to array element c[ 11 ]. Note that an indexed array name is an array-access expression. Such expressions can be used on the left side of an assignment to place a new value into an array element.

Common Programming Error 7.1

Image

Using a value of type long as an array index results in a compilation error. An index must be an int value or a value of a type that can be promoted to int—namely, byte, short or char, but not long.

Let us examine array c in Fig. 7.1 more closely. The name of the array is c. Every array object knows its own length and maintains this information in a length field. The expression c.length accesses array c’s length field to determine the length of the array. Note that, even though the length member of an array is public, it cannot be changed because it is a final variable. This array’s 12 elements are referred to as c[0], c[1], c[2],..., c[ 11 ]. The value of c[0] is -45, the value of c[1] is 6, the value of c[2] is 0, the value of c[7] is 62 and the value of c[ 11 ] is 78. To calculate the sum of the values contained in the first three elements of array c and store the result in variable sum, we would write

sum = c[ 0 ] + c[ 1 ] + c[ 2 ];

To divide the value of c[ 6 ] by 2 and assign the result to the variable x, we would write

x = c[ 6 ] / 2;

7.3 Declaring and Creating Arrays

Array objects occupy space in memory. Like other objects, arrays are created with keyword new. To create an array object, the programmer specifies the type of the array elements and the number of elements as part of an array-creation expression that uses keyword new. Such an expression returns a reference that can be stored in an array variable. The following declaration and array-creation expression create an array object containing 12 int elements and store the array’s reference in variable c:

int c[] = new int12 ];

This expression can be used to create the array shown in Fig. 7.1. This task also can be performed in two steps as follows:

Image

In the declaration, the square brackets following the variable name c indicate that c is a variable that will refer to an array (i.e., the variable will store an array reference). In the assignment statement, the array variable c receives the reference to a new array of 12 int elements. When an array is created, each element of the array receives a default value—zero for the numeric primitive-type elements, false for boolean elements and null for references (any nonprimitive type). As we’ll soon see, we can provide specific, nondefault initial element values when we create an array.

Common Programming Error 7.2

Image

In an array declaration, specifying the number of elements in the square brackets of the declaration (e.g., int c[ 12 ];) is a syntax error.

A program can create several arrays in a single declaration. The following String array declaration reserves 100 elements for b and 27 elements for x:

String b[] = new String[ 100 ], x[] = new String[ 27 ];

In this case, the class name String applies to each variable in the declaration. For readability, we prefer to declare only one variable per declaration, as in:

String b[] = new String[ 100 ]; // create array b
String x[] = new String[ 27 ]; // create array x

When an array is declared, the type of the array and the square brackets can be combined at the beginning of the declaration to indicate that all the identifiers in the declaration are array variables. For example, the declaration

double[] array1, array2;

indicates that array1 and array2 are each “array of double” variables. The preceding declaration is equivalent to:

double array1[];
double array2[];

or

double[] array1;
double[] array2;

The preceding pairs of declarations are equivalent—when only one variable is declared in each declaration, the square brackets can be placed either after the type or after the array variable name.

Common Programming Error 7.3

Image

Declaring multiple array variables in a single declaration can lead to subtle errors. Consider the declaration int[] a, b, c;. If a, b and c should be declared as array variables, then this declaration is correct—placing square brackets directly following the type indicates that all the identifiers in the declaration are array variables. However, if only a is intended to be an array variable, and b and c are intended to be individual int variables, then this declaration is incorrect—the declaration int a[], b, c; would achieve the desired result.

A program can declare arrays of any type. Every element of a primitive-type array contains a value of the array’s declared type. Similarly, in an array of a reference type, every element is a reference to an object of the array’s declared type. For example, every element of an int array is an int value, and every element of a String array is a reference to a String object.

7.4 Examples Using Arrays

This section presents several examples that demonstrate declaring arrays, creating arrays, initializing arrays and manipulating array elements.

Creating and Initializing an Array

The application of Fig. 7.2 uses keyword new to create an array of 10 int elements, which are initially zero (the default for int variables). Line 8 declares array—a reference capable of referring to an array of int elements. Line 10 creates the array object and assigns its reference to variable array. Line 12 outputs the column headings. The first column contains the index (0–9) of each array element, and the second column contains the default value (0) of each array element.

Fig. 7.2. Initializing the elements of an array to default values of zero.

Image

The for statement in lines 15–16 outputs the index number (represented by counter) and the value of each array element (represented by array[ counter ]). Note that the loop control variable counter is initially 0—index values start at 0, so using zero-based counting allows the loop to access every element of the array. The for’s loop-continuation condition uses the expression array.length (line 15) to determine the length of the array. In this example, the length of the array is 10, so the loop continues executing as long as the value of control variable counter is less than 10. The highest index value of a 10-element array is 9, so using the less-than operator in the loop-continuation condition guarantees that the loop does not attempt to access an element beyond the end of the array (i.e., during the final iteration of the loop, counter is 9). We’ll soon see what Java does when it encounters such an out-of-range index at execution time.

Using an Array Initializer

A program can create an array and initialize its elements with an array initializer, which is a comma-separated list of expressions (called an initializer list) enclosed in braces ({ and }); the array length is determined by the number of elements in the initializer list. For example, the declaration

int n[] = { 1020304050 };

creates a five-element array with index values 0, 1, 2, 3 and 4. Element n[0] is initialized to 10, n[ 1 ] is initialized to 20, and so on. This declaration does not require new to create the array object. When the compiler encounters an array declaration that includes an initializer list, it counts the number of initializers in the list to determine the size of the array, then sets up the appropriate new operation “behind the scenes.”

The application in Fig. 7.3 initializes an integer array with 10 values (line 9) and displays the array in tabular format. The code for displaying the array elements (lines 14–15) is identical to that in Fig. 7.2 (lines 15–16).

Fig. 7.3. Initializing the elements of an array with an array initializer.

Image

Calculating the Values to Store in an Array

The application in Fig. 7.4 creates a 10-element array and assigns to each element one of the even integers from 2 to 20 (2, 4, 6,..., 20). Then the application displays the array in tabular format. The for statement at lines 12–13 calculates an array element’s value by multiplying the current value of the control variable counter by 2, then adding 2.

Fig. 7.4. Calculating the values to be placed into the elements of an array.

Image

Line 8 uses the modifier final to declare the constant variable ARRAY_LENGTH with the value 10. Constant variables (also known as final variables) must be initialized before they are used and cannot be modified thereafter. If you attempt to modify a final variable after it is initialized in its declaration (as in line 8), the compiler issues the error message

cannot assign a value to final variable variableName

If an attempt is made to access the value of a final variable before it is initialized, the compiler issues the error message

variable variableName might not have been initialized

Good Programming Practice 7.1

Image

Constant variables also are called named constants or read-only variables. Such variables often make programs more readable than programs that use literal values (e.g., 10)—a named constant such as ARRAY_LENGTH clearly indicates its purpose, whereas a literal value could have different meanings based on the context in which it is used.

Common Programming Error 7.4

Image

Assigning a value to a constant after the variable has been initialized is a compilation error.

Common Programming Error 7.5

Image

Attempting to use a constant before it is initialized is a compilation error.

Summing the Elements of an Array

Often, the elements of an array represent a series of values to be used in a calculation. For example, if the elements of an array represent exam grades, a professor may wish to total the elements of the array and use that sum to calculate the class average for the exam. The examples using class GradeBook later in the chapter, namely Fig. 7.14 and Fig. 7.18, use this technique.

The application in Fig. 7.5 sums the values contained in a 10-element integer array. The program declares, creates and initializes the array at line 8. The for statement performs the calculations. [Note: The values supplied as array initializers are often read into a program rather than specified in an initializer list. For example, an application could input the values from a user or from a file on disk (as discussed in Chapter 14, Files and Streams). Reading the data into a program makes the program more reusable, because it can be used with different sets of data.]

Fig. 7.5. Computing the sum of the elements of an array.

Image

Using Bar Charts to Display Array Data Graphically

Many programs present data to users in a graphical manner. For example, numeric values are often displayed as bars in a bar chart. In such a chart, longer bars represent proportionally larger numeric values. One simple way to display numeric data graphically is with a bar chart that shows each numeric value as a bar of asterisks (*).

Professors often like to examine the distribution of grades on an exam. A professor might graph the number of grades in each of several categories to visualize the grade distribution. Suppose the grades on an exam were 87, 68, 94, 100, 83, 78, 85, 91, 76 and 87. Note that there was one grade of 100, two grades in the 90s, four grades in the 80s, two grades in the 70s, one grade in the 60s and no grades below 60. Our next application (Fig. 7.6) stores this grade distribution data in an array of 11 elements, each corresponding to a category of grades. For example, array[ 0 ] indicates the number of grades in the range 0–9, array[ 7 ] indicates the number of grades in the range 70–79 and array[ 10 ] indicates the number of 100 grades. The two versions of class GradeBook later in the chapter (Fig. 7.14 and Fig. 7.18) contain code that calculates these grade frequencies based on a set of grades. For now, we manually create the array by looking at the set of grades.

Fig. 7.6. Bar chart printing program.

Image

The application reads the numbers from the array and graphs the information as a bar chart. The program displays each grade range followed by a bar of asterisks indicating the number of grades in that range. To label each bar, lines 16–20 output a grade range (e.g., "70-79: ") based on the current value of counter. When counter is 10, line 17 outputs 100 with a field width of 5, followed by a colon and a space, to align the label "100: " with the other bar labels. The nested for statement (lines 23–24) outputs the bars. Note the loop-continuation condition at line 23 (stars < array[ counter ]). Each time the program reaches the inner for, the loop counts from 0 up to array[ counter ], thus using a value in array to determine the number of asterisks to display. In this example, array[ 0 ]–]array[ 5 ] contain zeroes because no students received a grade below 60. Thus, the program displays no asterisks next to the first six grade ranges. Note that line 19 uses the format specifier %02d to output the numbers in a grade range. This specifier indicates that an int value should be formatted as a field of two digits. The 0 flag in the format specifier indicates that values with fewer digits than the field width (2) should begin with a leading 0.

Using the Elements of an Array as Counters

Sometimes, programs use counter variables to summarize data, such as the results of a survey. In Fig. 6.7, we used separate counters in our die-rolling program to track the number of occurrences of each side of a die as the program rolled the die 6000 times. An array version of the application in Fig. 6.7 is shown in Fig. 7.7.

Fig. 7.7. Die-rolling program using arrays instead of switch.

Image

Fig. 7.7 uses the array frequency (line 10) to count the occurrences of each side of the die. The single statement in line 14 of this program replaces lines 23–46 of Fig. 6.7. Line 14 uses the random value to determine which frequency element to increment during each iteration of the loop. The calculation in line 14 produces random numbers from 1 to 6, so the array frequency must be large enough to store six counters. However, we use a seven-element array in which we ignore frequency[ 0 ]—it is more logical to have the face value 1 increment frequency[ 1 ] than frequency[ 0 ]. Thus, each face value is used as an index for array frequency. We also replaced lines 50–52 from Fig. 6.7 by looping through array frequency to output the results (lines 19–20).

Using Arrays to Analyze Survey Results

Our next example uses arrays to summarize the results of data collected in a survey:

Forty students were asked to rate the quality of the food in the student cafeteria on a scale of 1 to 10 (where 1 means awful and 10 means excellent). Place the 40 responses in an integer array, and summarize the results of the poll.

This is a typical array-processing application (see Fig. 7.8). We wish to summarize the number of responses of each type (i.e., 1 through 10). The array responses (lines 9–11) is a 40-element integer array of the students’ responses to the survey. We use an 11-element array frequency (line 12) to count the number of occurrences of each response. Each element of the array is used as a counter for one of the survey responses and is initialized to zero by default. As in Fig. 7.7, we ignore frequency[ 0 ].

Fig. 7.8. Poll analysis program.

Image

The for loop at lines 16–17 takes the responses one at a time from array responses and increments one of the 10 counters in the frequency array (frequency[ 1 ] to frequency[ 10 ]). The key statement in the loop is line 17, which increments the appropriate frequency counter, depending on the value of responses[ answer ].

Let’s consider several iterations of the for loop. When control variable answer is 0, the value of responses[ answer ] is the value of responses[ 0 ] (i.e., 1), so the program interprets ++frequency[ responses[ answer ] ] as

++frequency[ 1 ]

which increments the value in array element 1. To evaluate the expression, start with the value in the innermost set of square brackets (answer). Once you know answer’s value (which is the value of the loop control variable in line 16), plug it into the expression and evaluate the next outer set of square brackets (i.e., responses[ answer ], which is a value selected from the responses array in lines 9–11). Then use the resulting value as the index for the frequency array to specify which counter to increment.

When answer is 1, responses[ answer ] is the value of responses[ 1 ] (2), so the program interprets ++frequency[ responses[ answer ] ] as

++frequency[ 2 ]

which increments array element 2.

When answer is 2, responses[ answer ] is the value of responses[ 2 ] (6), so the program interprets ++frequency[ responses[ answer ] ] as

++frequency[ 6 ]

which increments array element 6, and so on. Regardless of the number of responses processed in the survey, the program requires only an 11-element array (ignoring element zero) to summarize the results, because all the response values are between 1 and 10 and the index values for an 11-element array are 0 through 10.

If the data in the responses array had contained invalid values, such as 13, the program would have attempted to add 1 to frequency[ 13 ], which is outside the bounds of the array. Java disallows this. When a Java program executes, the JVM checks array indices to ensure that they are valid (i.e., they must be greater than or equal to 0 and less than the length of the array). If a program uses an invalid index, Java generates a so-called exception to indicate that an error occurred in the program at execution time. A control statement can be used to prevent such an “out-of-bounds” error from occurring. For example, the condition in a control statement could determine whether an index is valid before allowing it to be used in an array-access expression.

Error-Prevention Tip 7.1

Image

An exception indicates that an error has occurred in a program. A programmer often can write code to recover from an exception and continue program execution, rather than abnormally terminating the program. When a program attempts to access an element outside the array bounds, an ArrayIndexOutOfBoundsException occurs. Exception handling is discussed in Chapter 13.

Error-Prevention Tip 7.2

Image

When writing code to loop through an array, ensure that the array index is always greater than or equal to 0 and less than the length of the array. The loop-continuation condition should prevent the accessing of elements outside this range.

7.5 Case Study: Card Shuffling and Dealing Simulation

The examples in the chapter thus far have used arrays containing elements of primitive types. Recall from Section 7.2 that the elements of an array can be either primitive types or reference types. This section uses random number generation and an array of referencetype elements, namely objects representing playing cards, to develop a class that simulates card shuffling and dealing. This class can then be used to implement applications that play specific card games.

We first develop class Card (Fig. 7.9), which represents a playing card that has a face (e.g., "Ace", "Deuce", "Three",..., "Jack", "Queen", "King") and a suit (e.g., "Hearts", "Diamonds", "Clubs", "Spades"). Next, we develop the DeckOfCards class (Fig. 7.10), which creates a deck of 52 playing cards in which each element is a Card object. We then build a test application (Fig. 7.11) that demonstrates class DeckOfCards’s card shuffling and dealing capabilities.

Fig. 7.9. Card class represents a playing card.

Image

Fig. 7.10. DeckOfCards class represents a deck of playing cards that can be shuffled and dealt one at a time.

Image

Image

Fig. 7.11. Card shuffling and dealing.

Image

Class Card

Class Card (Fig. 7.9) contains two String instance variables—face and suit—that are used to store references to the face name and suit name for a specific Card. The constructor for the class (lines 10–14) receives two Strings that it uses to initialize face and suit. Method toString (lines 17–20) creates a String consisting of the face of the card, the String " of " and the suit of the card. Recall from Chapter 6 that the + operator can be used to concatenate (i.e., combine) several Strings to form one larger String. Card’s toString method can be invoked explicitly to obtain a string representation of a Card object (e.g., "Ace of Spades"). The toString method of an object is called implicitly when the object is used where a String is expected (e.g., when printf outputs the object as a String using the %s format specifier or when the object is concatenated to a String using the + operator). For this behavior to occur, toString must be declared with the header shown in Fig. 7.9.

Class DeckOfCards

Class DeckOfCards (Fig. 7.10) declares an instance variable array named deck of Card objects (line 7). Like primitive-type array declarations, the declaration of an array of objects includes the type of the elements in the array, followed by the name of the array variable and square brackets (e.g., Card deck[]). Class DeckOfCards also declares an integer instance variable currentCard (line 8) representing the next Card to be dealt from the deck array and a named constant NUMBER_OF_CARDS (line 9) indicating the number of Cards in the deck (52).

The class’s constructor instantiates the deck array (line 19) to be of size NUMBER_OF_CARDS. When first created, the elements of the deck array are null by default, so the constructor uses a for statement (lines 24–26) to fill the deck array with Cards. The for statement initializes control variable count to 0 and loops while count is less than deck.length, causing count to take on each integer value from 0 to 51 (the indices of the deck array). Each Card is instantiated and initialized with two Strings—one from the faces array (which contains the Strings "Ace" through "King") and one from the suits array (which contains the Strings "Hearts", "Diamonds", "Clubs" and "Spades"). The calculation count % 13 always results in a value from 0 to 12 (the 13 indices of the faces array in lines 15–16), and the calculation count / 13 always results in a value from 0 to 3 (the four indices of the suits array in line 17). When the deck array is initialized, it contains the Cards with faces "Ace" through "King" in order for each suit ("Hearts" then "Diamonds" then "Clubs" then "Spades").

Method shuffle (lines 30–46) shuffles the Cards in the deck. The method loops through all 52 Cards (array indices 0 to 51). For each Card, a number between 0 and 51 is picked randomly to select another Card. Next, the current Card object and the randomly selected Card object are swapped in the array. This exchange is performed by the three assignments in lines 42–44. The extra variable temp temporarily stores one of the two Card objects being swapped. The swap cannot be performed with only the two statements

deck[ first ] = deck[ second ];
deck[ second ] = deck[ first ];

If deck[ first ] is the "Ace" of "Spades" and deck[ second ] is the "Queen" of "Hearts", after the first assignment, both array elements contain the "Queen" of "Hearts" and the "Ace" of "Spades" is lost—hence, the extra variable temp is needed. After the for loop terminates, the Card objects are randomly ordered. A total of only 52 swaps are made in a single pass of the entire array, and the array of Card objects is shuffled!

Method dealCard (lines 49–56) deals one Card in the array. Recall that currentCard indicates the index of the next Card to be dealt (i.e., the Card at the top of the deck). Thus, line 52 compares currentCard to the length of the deck array. If the deck is not empty (i.e., currentCard is less than 52), line 53 returns the “top” Card and postincrements currentCard to prepare for the next call to dealCard—otherwise, null is returned. Recall from Chapter 3 that null represents a “reference to nothing.”

Shuffling and Dealing Cards

The application of Fig. 7.11 demonstrates the card dealing and shuffling capabilities of class DeckOfCards (Fig. 7.10). Line 9 creates a DeckOfCards object named myDeckOfCards. Recall that the DeckOfCards constructor creates the deck with the 52 Card objects in order by suit and face. Line 10 invokes myDeckOfCards’s shuffle method to rearrange the Card objects. The for statement in lines 13–19 deals all 52 Cards in the deck and prints them in four columns of 13 Cards each. Lines 16–18 deal and print four Card objects, each obtained by invoking myDeckOfCards’s dealCard method. When printf outputs a Card with the %-20s format specifier, the Card’s toString method (declared in lines 17–20 of Fig. 7.9) is implicitly invoked, and the result is output left justified in a field of width 20.

7.6 Enhanced for Statement

In previous examples, we demonstrated how to use counter-controlled for statements to iterate through the elements of an array. In this section, we introduce the enhanced for statement, which iterates through the elements of an array or a collection without using a counter (thus avoiding the possibility of “stepping outside” the array). This section discusses how to use the enhanced for statement to loop through an array. We show how to use the enhanced for statement with collections in Chapter 16. The syntax of an enhanced for statement is:

for ( parameter : arrayName )
   statement

where parameter has two parts—a type and an identifier (e.g., int number)—and arrayName is the array through which to iterate. The type of the parameter must be consistent with the type of the elements in the array. As the next example illustrates, the identifier represents successive values in the array on successive iterations of the enhanced for statement.

Figure 7.12 uses the enhanced for statement (lines 12–13) to sum the integers in an array of student grades. The type specified in the parameter to the enhanced for is int, because array contains int values—the loop selects one int value from the array during each iteration. The enhanced for statement iterates through successive values in the array one by one. The enhanced for header can be read as “for each iteration, assign the next element of array to int variable number, then execute the following statement.” Thus, for each iteration, identifier number represents an int value in array. Lines 12–13 are equivalent to the following counter-controlled repetition used in lines 12–13 of Fig. 7.5 to total the integers in array:

Image

Fig. 7.12. Using the enhanced for statement to total integers in an array.

Image

The enhanced for statement simplifies the code for iterating through an array. Note, however, that the enhanced for statement can be used only to obtain array elements—it cannot be used to modify elements. If your program needs to modify elements, use the traditional counter-controlled for statement.

The enhanced for statement can be used in place of the counter-controlled for statement whenever code looping through an array does not require access to the counter indicating the index of the current array element. For example, totaling the integers in an array requires access only to the element values—the index of each element is irrelevant. However, if a program must use a counter for some reason other than simply to loop through an array (e.g., to print an index number next to each array element value, as in the examples earlier in this chapter), use the counter-controlled for statement.

7.7 Passing Arrays to Methods

This section demonstrates how to pass arrays and individual array elements as arguments to methods. At the end of the section, we discuss how all types of arguments are passed to methods. To pass an array argument to a method, specify the name of the array without any brackets. For example, if array hourlyTemperatures is declared as

double hourlyTemperatures[] = new double24 ];

then the method call

modifyArray( hourlyTemperatures );

passes the reference of array hourlyTemperatures to method modifyArray. Every array object “knows” its own length (via its length field). Thus, when we pass an array object’s reference into a method, we need not pass the array length as an additional argument.

For a method to receive an array reference through a method call, the method’s parameter list must specify an array parameter. For example, the method header for method modifyArray might be written as

void modifyArray( int b[] )

indicating that modifyArray receives the reference of an integer array in parameter b. The method call passes array hourlyTemperature’s reference, so when the called method uses the array variable b, it refers to the same array object as hourlyTemperatures in the caller.

When an argument to a method is an entire array or an individual array element of a reference type, the called method receives a copy of the reference. However, when an argument to a method is an individual array element of a primitive type, the called method receives a copy of the element’s value. Such primitive values are called scalars or scalar quantities. To pass an individual array element to a method, use the indexed name of the array element as an argument in the method call.

Figure 7.13 demonstrates the difference between passing an entire array and passing a primitive-type array element to a method. The enhanced for statement at lines 16–17 outputs the five elements of array (an array of int values). Line 19 invokes method modifyArray, passing array as an argument. Method modifyArray (lines 36–40) receives a copy of array’s reference and uses the reference to multiply each of array’s elements by 2. To prove that array’s elements were modified, the for statement at lines 23–24 outputs the five elements of array again. As the output shows, method modifyArray doubled the value of each element. Note that we could not use the enhanced for statement in lines 38–39 because we are modifying the array’s elements.

Fig. 7.13. Passing arrays and individual array elements to methods.

Image

Image

Figure 7.13 next demonstrates that when a copy of an individual primitive-type array element is passed to a method, modifying the copy in the called method does not affect the original value of that element in the calling method’s array. Lines 26–28 output the value of array[ 3 ] (8) before invoking method modifyElement. Line 30 calls method modifyElement and passes array[ 3 ] as an argument. Remember that array[ 3 ] is actually one int value (8) in array. Therefore, the program passes a copy of the value of array[ 3 ]. Method modifyElement (lines 43–48) multiplies the value received as an argument by 2, stores the result in its parameter element, then outputs the value of element (16). Since method parameters, like local variables, cease to exist when the method in which they are declared completes execution, the method parameter element is destroyed when method modifyElement terminates. Thus, when the program returns control to main, lines 31–32 output the unmodified value of array[ 3 ] (i.e., 8).

Notes on Passing Arguments to Methods

The preceding example demonstrated the different ways that arrays and primitive-type array elements are passed as arguments to methods. We now take a closer look at how arguments in general are passed to methods. Two ways to pass arguments in method calls in many programming languages are pass-by-value and pass-by-reference (also called call-by-value and call-by-reference). When an argument is passed by value, a copy of the argument’s value is passed to the called method. The called method works exclusively with the copy. Changes to the called method’s copy do not affect the original variable’s value in the caller.

When an argument is passed by reference, the called method can access the argument’s value in the caller directly and modify that data, if necessary. Pass-by-reference improves performance by eliminating the need to copy possibly large amounts of data.

Unlike some other languages, Java does not allow programmers to choose pass-by-value or pass-by-reference—all arguments are passed by value. A method call can pass two types of values to a method—copies of primitive values (e.g., values of type int and double) and copies of references to objects (including references to arrays). Objects themselves cannot be passed to methods. When a method modifies a primitive-type parameter, changes to the parameter have no effect on the original argument value in the calling method. For example, when line 30 in main of Fig. 7.13 passes array[ 3 ] to method modifyElement, the statement in line 45 that doubles the value of parameter element has no effect on the value of array[ 3 ] in main. This is also true for reference-type parameters. If you modify a reference-type parameter by assigning it the reference of another object, the parameter refers to the new object, but the reference stored in the caller’s variable still refers to the original object.

Although an object’s reference is passed by value, a method can still interact with the referenced object by calling its public methods using the copy of the object’s reference. Since the reference stored in the parameter is a copy of the reference that was passed as an argument, the parameter in the called method and the argument in the calling method refer to the same object in memory. For example, in Fig. 7.13, both parameter array2 in method modifyArray and variable array in main refer to the same array object in memory. Any changes made using the parameter array2 are carried out on the same object that is referenced by the variable that was passed as an argument in the calling method. In Fig. 7.13, the changes made in modifyArray using array2 affect the contents of the array object referenced by array in main. Thus, with a reference to an object, the called method can manipulate the caller’s object directly.

Performance Tip 7.1

Image

Passing arrays by reference makes sense for performance reasons. If arrays were passed by value, a copy of each element would be passed. For large, frequently passed arrays, this would waste time and consume considerable storage for the copies of the arrays.

7.8 Case Study: Class GradeBook Using an Array to Store Grades

This section further evolves class GradeBook, introduced in Chapter 3 and expanded in Chapters 45. Recall that this class represents a grade book used by a professor to store and analyze a set of student grades. Previous versions of the class process a set of grades entered by the user, but do not maintain the individual grade values in instance variables of the class. Thus, repeat calculations require the user to reenter the same grades. One way to solve this problem would be to store each grade entered in an individual instance of the class. For example, we could create instance variables grade1, grade2,..., grade10 in class GradeBook to store 10 student grades. However, the code to total the grades and determine the class average would be cumbersome, and the class would not be able to process any more than 10 grades at a time. In this section, we solve this problem by storing grades in an array.

Storing Student Grades in an Array in Class GradeBook

The version of class GradeBook (Fig. 7.14) presented here uses an array of integers to store the grades of several students on a single exam. This eliminates the need to repeatedly input the same set of grades. Array grades is declared as an instance variable in line 7—therefore, each GradeBook object maintains its own set of grades. The class’s constructor (lines 10–14) has two parameters—the name of the course and an array of grades. When an application (e.g., class GradeBookTest in Fig. 7.15) creates a GradeBook object, the application passes an existing int array to the constructor, which assigns the array’s reference to instance variable grades (line 13). The size of the array grades is determined by the class that passes the array to the constructor. Thus, a GradeBook object can process a variable number of grades. The grade values in the passed array could have been input from a user or read from a file on disk (see Chapter 14). In our test application, we simply initialize an array with a set of grade values (Fig. 7.15, line 10). Once the grades are stored in instance variable grades of class GradeBook, all the class’s methods can access the elements of grades as often as needed to perform various calculations.

Fig. 7.14. GradeBook class using an array to store test grades.

Image

Image

Image

Image

Image

Fig. 7.15. GradeBookTest creates a GradeBook object using an array of grades, then invokes method processGrades to analyze them.

Image

Image

Method processGrades (lines 37–51) contains a series of method calls that output a report summarizing the grades. Line 40 calls method outputGrades to print the contents of the array grades. Lines 134–136 in method outputGrades use a for statement to output the students’ grades. A counter-controlled for must be used in this case, because lines 135–136 use counter variable student’s value to output each grade next to a particular student number (see Fig. 7.15). Although array indices start at 0, a professor would typically number students starting at 1. Thus, lines 135–136 output student + 1 as the student number to produce grade labels "Student 1: ", "Student 2: ", and so on.

Method processGrades next calls method getAverage (line 43) to obtain the average of the grades in the array. Method getAverage (lines 86–96) uses an enhanced for statement to total the values in array grades before calculating the average. The parameter in the enhanced for’s header (e.g., int grade) indicates that for each iteration, the int variable grade takes on a value in the array grades. Note that the averaging calculation in line 95 uses grades.length to determine the number of grades being averaged.

Lines 46–47 in method processGrades calls methods getMinimum and getMaximum to determine the lowest and highest grades of any student on the exam, respectively. Each of these methods uses an enhanced for statement to loop through array grades. Lines 59–64 in method getMinimum loop through the array. Lines 62–63 compare each grade to lowGrade; if a grade is less than lowGrade, lowGrade is set to that grade. When line 66 executes, lowGrade contains the lowest grade in the array. Method getMaximum (lines 70–83) works similarly to method getMinimum.

Finally, line 50 in method processGrades calls method outputBarChart to print a distribution chart of the grade data using a technique similar to that in Fig. 7.6. In that example, we manually calculated the number of grades in each category (i.e., 0–9, 10–19,..., 90–99 and 100) by simply looking at a set of grades. In this example, lines 107–108 use a technique similar to that in Fig. 7.7 and Fig. 7.8 to calculate the frequency of grades in each category. Line 104 declares and creates array frequency of 11 ints to store the frequency of grades in each grade category. For each grade in array grades, lines 107–108 increment the appropriate element of the frequency array. To determine which element to increment, line 108 divides the current grade by 10 using integer division. For example, if grade is 85, line 108 increments frequency[ 8 ] to update the count of grades in the range 80–89. Lines 111–125 next print the bar chart (see Fig. 7.15) based on the values in array frequency. Like lines 23–24 of Fig. 7.6, lines 121–122 of Fig. 7.14 use a value in array frequency to determine the number of asterisks to display in each bar.

Class GradeBookTest That Demonstrates Class GradeBook

The application of Fig. 7.15 creates an object of class GradeBook (Fig. 7.14) using the int array gradesArray (declared and initialized in line 10). Lines 12–13 pass a course name and gradesArray to the GradeBook constructor. Line 14 displays a welcome message, and line 15 invokes the GradeBook object’s processGrades method. The output summarizes the 10 grades in myGradeBook.

Software Engineering Observation 7.1

Image

A test harness (or test application) is responsible for creating an object of the class being tested and providing it with data. This data could come from any of several sources. Test data can be placed directly into an array with an array initializer, it can come from the user at the keyboard, it can come from a file (as you’ll see in Chapter 14), or it can come from a network (as you’ll see in Chapter 19). After passing this data to the class’s constructor to instantiate the object, the test harness should call upon the object to test its methods and manipulate its data. Gathering data in the test harness like this allows the class to manipulate data from several sources.

7.9 Multidimensional Arrays

Multidimensional arrays with two dimensions are often used to represent tables of values consisting of information arranged in rows and columns. To identify a particular table element, we must specify two indices. By convention, the first identifies the element’s row and the second its column. Arrays that require two indices to identify a particular element are called two-dimensional arrays. (Multidimensional arrays can have more than two dimensions.) Java does not support multidimensional arrays directly, but it does allow the programmer to specify one-dimensional arrays whose elements are also one-dimensional arrays, thus achieving the same effect. Figure 7.16 illustrates a two-dimensional array a that contains three rows and four columns (i.e., a three-by-four array). In general, an array with m rows and n columns is called an m-by-n array.

Fig. 7.16. Two-dimensional array with three rows and four columns.

Image

Every element in array a is identified in Fig. 7.16 by an array-access expression of the form a[ row ][ column ]; a is the name of the array, and row and column are the indices that uniquely identify each element in array a by row and column number. Note that the names of the elements in row 0 all have a first index of 0, and the names of the elements in column 3 all have a second index of 3.

Arrays of One-Dimensional Arrays

Like one-dimensional arrays, multidimensional arrays can be initialized in their declarations with array initializers. For example, a two-dimensional array named b with two rows and two columns could be declared and initialized with nested array initializers as follows:

int b[][] = { { 12 }, { 34 } };

The initializer values are grouped by row in braces. So 1 and 2 initialize b[ 0 ][ 0 ] and b[ 0 ][ 1 ], respectively, and 3 and 4 initialize b[ 1 ][ 0 ] and b[ 1 ][ 1 ], respectively. The compiler counts the number of nested array initializers (represented by sets of braces within the outer braces) in the array declaration to determine the number of rows in array b. The compiler counts the initializer values in the nested array initializer for a row to determine the number of columns in that row. As we’ll see momentarily, this means that rows can have different lengths.

Multidimensional arrays are maintained as arrays of one-dimensional arrays. Therefore array b in the preceding declaration is actually composed of two separate one-dimensional arrays—one containing the values in the first nested initializer list {1, 2} and one containing the values in the second nested initializer list {3, 4}. Thus, array b itself is an array of two elements, each a one-dimensional array of int values.

Two-Dimensional Arrays with Rows of Different Lengths

The manner in which multidimensional arrays are represented makes them quite flexible. In fact, the lengths of the rows in array b are not required to be the same. For example,

int b[][] = { { 12 }, { 345 } };

creates integer array b with two elements (determined by the number of nested array initializers) that represent the rows of the two-dimensional array. Each element of b is a reference to a one-dimensional array of int variables. The int array for row 0 is a one-dimensional array with two elements (1 and 2), and the int array for row 1 is a one-dimensional array with three elements (3, 4 and 5).

Creating Two-Dimensional Arrays with Array-Creation Expressions

A multidimensional array with the same number of columns in every row can be created with an array-creation expression. For example, the following lines declare array b and assign it a reference to a three-by-four array:

int b[][] = new int3 ][ 4 ];

In this case, we use the literal values 3 and 4 to specify the number of rows and number of columns, respectively, but this is not required. Programs can also use variables to specify array dimensions, because new creates arrays at execution time—not at compile time. As with one-dimensional arrays, the elements of a multidimensional array are initialized when the array object is created.

A multidimensional array in which each row has a different number of columns can be created as follows:

Image

The preceding statements create a two-dimensional array with two rows. Row 0 has five columns, and row 1 has three columns.

Two-Dimensional Array Example: Displaying Element Values

Figure 7.17 demonstrates initializing two-dimensional arrays with array initializers and using nested for loops to traverse the arrays (i.e., manipulate every element of each array).

Fig. 7.17. Initializing two-dimensional arrays.

Image

Class InitArray’s main declares two arrays. The declaration of array1 (line 9) uses nested array initializers to initialize the first row of the array to the values 1, 2 and 3, and the second row to the values 4, 5 and 6. The declaration of array2 (line 10) uses nested initializers of different lengths. In this case, the first row is initialized to have two elements with the values 1 and 2, respectively. The second row is initialized to have one element with the value 3. The third row is initialized to have three elements with the values 4, 5 and 6, respectively.

Lines 13 and 16 call method outputArray (lines 20–31) to output the elements of array1 and array2, respectively. Method outputArray specifies the array parameter as int array[][] to indicate that the method receives a two-dimensional array. The for statement (lines 23–30) outputs the rows of a two-dimensional array. In the loop-continuation condition of the outer for statement, the expression array.length determines the number of rows in the array. In the inner for statement, the expression array[ row ].length determines the number of columns in the current row of the array. This condition enables the loop to determine the exact number of columns in each row.

Common Multidimensional-Array Manipulations Performed with for Statements

Many common array manipulations use for statements. As an example, the following for statement sets all the elements in row 2 of array a in Fig. 7.16 to zero:

for ( int column = 0; column < a[ 2 ].length; column++)
   a[ 2 ][ column ] = 0;

We specified row 2; therefore, we know that the first index is always 2 (0 is the first row, and 1 is the second row). This for loop varies only the second index (i.e., the column index). If row 2 of array a contains four elements, then the preceding for statement is equivalent to the assignment statements

a[ 2 ][ 0 ] = 0;
a[ 2 ][ 1 ] = 0;
a[ 2 ][ 2 ] = 0;
a[ 2 ][ 3 ] = 0;

The following nested for statement totals the values of all the elements in array a:

Image

This nested for statements total the array elements one row at a time. The outer for statement begins by setting the row index to 0 so that the first row’s elements can be totaled by the inner for statement. The outer for then increments row to 1 so that the second row can be totaled. Then, the outer for increments row to 2 so that the third row can be totaled. The variable total can be displayed when the outer for statement terminates. In the next example, we show how to process a two-dimensional array in a similar manner using nested enhanced for statements.

7.10 Case Study: Class GradeBook Using a Two-Dimensional Array

In Section 7.8, we presented class GradeBook (Fig. 7.14), which used a one-dimensional array to store student grades on a single exam. In most semesters, students take several exams. Professors are likely to want to analyze grades across the entire semester, both for a single student and for the class as a whole.

Storing Student Grades in a Two-Dimensional Array in Class GradeBook

Figure 7.18 contains a version of class GradeBook that uses a two-dimensional array grades to store the grades of a number of students on multiple exams. Each row of the array represents a single student’s grades for the entire course, and each column represents a grade on one of the exams the students took during the course. An application such as GradeBookTest (Fig. 7.19) passes the array as an argument to the GradeBook constructor. In this example, we use a ten-by-three array containing ten students’ grades on three exams. Five methods perform array manipulations to process the grades. Each method is similar to its counterpart in the earlier one-dimensional array version of class GradeBook (Fig. 7.14). Method getMinimum (lines 52–70) determines the lowest grade of any student for the semester. Method getMaximum (lines 73–91) determines the highest grade of any student for the semester. Method getAverage (lines 94–104) determines a particular student’s semester average. Method outputBarChart (lines 107–137) outputs a bar chart of the distribution of all student grades for the semester. Method outputGrades (lines 140–164) outputs the two-dimensional array in a tabular format, along with each student’s semester average.

Fig. 7.18. GradeBook class using a two-dimensional array to store grades.

Image

Image

Image

Image

Image

Image

Methods getMinimum, getMaximum, outputBarChart and outputGrades each loop through array grades by using nested for statements—for example, the nested enhanced for statement from the declaration of method getMinimum (lines 58–67). The outer enhanced for statement iterates through the two-dimensional array grades, assigning successive rows to parameter studentGrades on successive iterations. The square brackets following the parameter name indicate that studentGrades refers to a one-dimensional int array—namely, a row in array grades containing one student’s grades. To find the lowest overall grade, the inner for statement compares the elements of the current one-dimensional array studentGrades to variable lowGrade. For example, on the first iteration of the outer for, row 0 of grades is assigned to parameter studentGrades. The inner enhanced for statement then loops through studentGrades and compares each grade value with lowGrade. If a grade is less than lowGrade, lowGrade is set to that grade. On the second iteration of the outer enhanced for statement, row 1 of grades is assigned to studentGrades, and the elements of this row are compared with variable lowGrade. This repeats until all rows of grades have been traversed. When execution of the nested statement is complete, lowGrade contains the lowest grade in the two-dimensional array. Method getMaximum works similarly to method getMinimum.

Method outputBarChart in Fig. 7.18 is nearly identical to the one in Fig. 7.14. However, to output the overall grade distribution for a whole semester, the method here uses a nested enhanced for statement (lines 115–119) to create the one-dimensional array frequency based on all the grades in the two-dimensional array. The rest of the code in each of the two outputBarChart methods that displays the chart is identical.

Method outputGrades (lines 140–164) also uses nested for statements to output values of the array grades and each student’s semester average. The output in Fig. 7.19 shows the result, which resembles the tabular format of a professor’s physical grade book. Lines 146–147 print the column headings for each test. We use a counter-controlled for statement here so that we can identify each test with a number. Similarly, the for statement in lines 152–163 first outputs a row label using a counter variable to identify each student (line 154). Although array indices start at 0, note that lines 147 and 154 output test + 1 and student + 1, respectively, to produce test and student numbers starting at 1 (see Fig. 7.19). The inner for statement in lines 156–157 uses the outer for statement’s counter variable student to loop through a specific row of array grades and output each student’s test grade. Note that an enhanced for statement can be nested in a counter-controlled for statement, and vice versa. Finally, line 161 obtains each student’s semester average by passing the current row of grades (i.e., grades[ student ]) to method getAverage.

Method getAverage (lines 94–104) takes one argument—a one-dimensional array of test results for a particular student. When line 161 calls getAverage, the argument is grades[ student ], which specifies that a particular row of the two-dimensional array grades should be passed to getAverage. For example, based on the array created in Fig. 7.19, the argument grades[ 1 ] represents the three values (a one-dimensional array of grades) stored in row 1 of the two-dimensional array grades. Recall that a two-dimensional array is an array whose elements are one-dimensional arrays. Method getAverage calculates the sum of the array elements, divides the total by the number of test results and returns the floating-point result as a double value (line 103).

Class GradeBookTest That Demonstrates Class GradeBook

The application in Fig. 7.19 creates an object of class GradeBook (Fig. 7.18) using the two-dimensional array of ints named gradesArray (declared and initialized in lines 10–19). Lines 21–22 pass a course name and gradesArray to the GradeBook constructor. Lines 23–24 then invoke myGradeBook’s displayMessage and processGrades methods to display a welcome message and obtain a report summarizing the students’ grades for the semester, respectively.

Fig. 7.19. Creates GradeBook object using a two-dimensional array of grades, then invokes method processGrades to analyze them.

Image

Image

7.11 Variable-Length Argument Lists

With variable-length argument lists, you can create methods that receive an unspecified number of arguments. An argument type followed by an ellipsis (...) in a method’s parameter list indicates that the method receives a variable number of arguments of that particular type. This use of the ellipsis can occur only once in a parameter list, and the ellipsis, together with its type, must be placed at the end of the parameter list. While programmers can use method overloading and array passing to accomplish much of what is accomplished with “varargs,” or variable-length argument lists, using an ellipsis in a method’s parameter list is more concise.

Figure 7.20 demonstrates method average (lines 7–16), which receives a variable-length sequence of doubles. Java treats the variable-length argument list as an array whose elements are all of the same type. Hence, the method body can manipulate the parameter numbers as an array of doubles. Lines 12–13 use the enhanced for loop to walk through the array and calculate the total of the doubles in the array. Line 15 accesses numbers.length to obtain the size of the numbers array for use in the averaging calculation. Lines 29, 31 and 33 in main call method average with two, three and four arguments, respectively. Method average has a variable-length argument list (line 7), so it can average as many double arguments as the caller passes. The output shows that each call to method average returns the correct value.

Fig. 7.20. Using variable-length argument lists.

Image

Common Programming Error 7.6

Image

Placing an ellipsis indicating a variable-length argument list in the middle of a method parameter list is a syntax error. An ellipsis may be placed only at the end of the parameter list.

7.12 Using Command-Line Arguments

On many systems it is possible to pass arguments from the command line (these are known as command-line arguments) to an application by including a parameter of type String[] (i.e., an array of Strings) in the parameter list of main, exactly as we have done in every application in the book. By convention, this parameter is named args. When an application is executed using the java command, Java passes the command-line arguments that appear after the class name in the java command to the application’s main method as Strings in the array args. The number of arguments passed in from the command line is obtained by accessing the array’s length attribute. For example, the command "java MyClass a b" passes two command-line arguments, a and b, to application MyClass. Note that command-line arguments are separated by white space, not commas. When this command executes, MyClass’s main method receives the two-element array args (i.e., args.length is 2) in which args[ 0 ] contains the String "a" and args[ 1 ] contains the String "b". Common uses of command-line arguments include passing options and file names to applications.

Figure 7.21 uses three command-line arguments to initialize an array. When the program executes, if args.length is not 3, the program prints an error message and terminates (lines 9–12). Otherwise, lines 14–32 initialize and display the array based on the values of the command-line arguments.

Fig. 7.21. Initializing an array using command-line arguments.

Image

Image

The command-line arguments become available to main as Strings in args. Line 16 gets args[ 0 ]—a String that specifies the array size—and converts it to an int value that the program uses to create the array in line 17. The static method parseInt of class Integer converts its String argument to an int.

Lines 20–21 convert the args[ 1 ] and args[ 2 ] command-line arguments to int values and store them in initialValue and increment, respectively. Lines 24–25 calculate the value for each array element.

The output of the first execution shows that the application received an insufficient number of command-line arguments. The second execution uses command-line arguments 5, 0 and 4 to specify the size of the array (5), the value of the first element (0) and the increment of each value in the array (4), respectively. The corresponding output shows that these values create an array containing the integers 0, 4, 8, 12 and 16. The output from the third execution shows that the command-line arguments 10, 1 and 2 produce an array whose 10 elements are the nonnegative odd integers from 1 to 19.

7.13 (Optional) Software Engineering Case Study: Collaboration Among Objects

In this section, we concentrate on the collaborations (interactions) among objects. When two objects communicate with each other to accomplish a task, they are said to collaborate—objects do this by invoking one another’s operations. A collaboration consists of an object of one class sending a message to an object of another class. Messages are sent in Java via method calls.

In Section 6.19, we determined many of the operations of the classes in our system. In this section, we concentrate on the messages that invoke these operations. To identify the collaborations in the system, we return to the requirements document in Section 2.8. Recall that this document specifies the range of activities that occur during an ATM session (e.g., authenticating a user, performing transactions). The steps used to describe how the system must perform each of these tasks are our first indication of the collaborations in our system. As we proceed through this and the remaining Software Engineering Case Study sections, we may discover additional collaborations.

Identifying the Collaborations in a System

We identify the collaborations in the system by carefully reading the sections of the requirements document that specify what the ATM should do to authenticate a user and to perform each transaction type. For each action or step described in the requirements document, we decide which objects in our system must interact to achieve the desired result. We identify one object as the sending object and another as the receiving object. We then select one of the receiving object’s operations (identified in Section 6.19) that must be invoked by the sending object to produce the proper behavior. For example, the ATM displays a welcome message when idle. We know that an object of class Screen displays a message to the user via its displayMessage operation. Thus, we decide that the system can display a welcome message by employing a collaboration between the ATM and the Screen in which the ATM sends a displayMessage message to the Screen by invoking the displayMessage operation of class Screen. [Note: To avoid repeating the phrase “an object of class...,” we refer to an object by using its class name preceded by an article (e.g., “a,” “an” or “the”)—for example, “the ATM” refers to an object of class ATM.]

Figure 7.22 lists the collaborations that can be derived from the requirements document. For each sending object, we list the collaborations in the order in which they first occur during an ATM session (i.e., the order in which they are discussed in the requirements document). We list each collaboration involving a unique sender, message and recipient only once, even though the collaborations may occur at several different times throughout an ATM session. For example, the first row in Fig. 7.22 indicates that the ATM collaborates with the Screen whenever the ATM needs to display a message to the user.

Fig. 7.22. Collaborations in the ATM system.

Image

Let’s consider the collaborations in Fig. 7.22. Before allowing a user to perform any transactions, the ATM must prompt the user to enter an account number, then to enter a PIN. It accomplishes each of these tasks by sending a displayMessage message to the Screen. Both of these actions refer to the same collaboration between the ATM and the Screen, which is already listed in Fig. 7.22. The ATM obtains input in response to a prompt by sending a getInput message to the Keypad. Next, the ATM must determine whether the user-specified account number and PIN match those of an account in the database. It does so by sending an authenticateUser message to the BankDatabase. Recall that the BankDatabase cannot authenticate a user directly—only the user’s Account (i.e., the Account that contains the account number specified by the user) can access the user’s PIN on record to authenticate the user. Figure 7.22 therefore lists a collaboration in which the BankDatabase sends a validatePIN message to an Account.

After the user is authenticated, the ATM displays the main menu by sending a series of displayMessage messages to the Screen and obtains input containing a menu selection by sending a getInput message to the Keypad. We have already accounted for these collaborations, so we do not add anything to Fig. 7.22. After the user chooses a type of transaction to perform, the ATM executes the transaction by sending an execute message to an object of the appropriate transaction class (i.e., a BalanceInquiry, a Withdrawal or a Deposit). For example, if the user chooses to perform a balance inquiry, the ATM sends an execute message to a BalanceInquiry.

Further examination of the requirements document reveals the collaborations involved in executing each transaction type. A BalanceInquiry retrieves the amount of money available in the user’s account by sending a getAvailableBalance message to the BankDatabase, which responds by sending a getAvailableBalance message to the user’s Account. Similarly, the BalanceInquiry retrieves the amount of money on deposit by sending a getTotalBalance message to the BankDatabase, which sends the same message to the user’s Account. To display both measures of the user’s balance at the same time, the BalanceInquiry sends a displayMessage message to the Screen.

A Withdrawal sends a series of displayMessage messages to the Screen to display a menu of standard withdrawal amounts (i.e., $20, $40, $60, $100, $200). The Withdrawal sends a getInput message to the Keypad to obtain the user’s menu selection. Next, the Withdrawal determines whether the requested withdrawal amount is less than or equal to the user’s account balance. The Withdrawal can obtain the amount of money available in the user’s account by sending a getAvailableBalance message to the BankDatabase. The Withdrawal then tests whether the cash dispenser contains enough cash by sending an isSufficientCashAvailable message to the CashDispenser. A Withdrawal sends a debit message to the BankDatabase to decrease the user’s account balance. The BankDatabase in turn sends the same message to the appropriate Account. Recall that debiting funds from an Account decreases both the totalBalance and the availableBalance. To dispense the requested amount of cash, the Withdrawal sends a dispenseCash message to the CashDispenser. Finally, the Withdrawal sends a displayMessage message to the Screen, instructing the user to take the cash.

A Deposit responds to an execute message first by sending a displayMessage message to the Screen to prompt the user for a deposit amount. The Deposit sends a getInput message to the Keypad to obtain the user’s input. The Deposit then sends a displayMessage message to the Screen to tell the user to insert a deposit envelope. To determine whether the deposit slot received an incoming deposit envelope, the Deposit sends an isEnvelopeReceived message to the DepositSlot. The Deposit updates the user’s account by sending a credit message to the BankDatabase, which subsequently sends a credit message to the user’s Account. Recall that crediting funds to an Account increases the totalBalance but not the availableBalance.

Interaction Diagrams

Now that we have identified a set of possible collaborations between the objects in our ATM system, let us graphically model these interactions using the UML. The UML provides several types of interaction diagrams that model the behavior of a system by modeling how objects interact. The communication diagram emphasizes which objects participate in collaborations. [Note: Communication diagrams were called collaboration diagrams in earlier versions of the UML.] Like the communication diagram, the sequence diagram shows collaborations among objects, but it emphasizes when messages are sent between objects over time.

Communication Diagrams

Figure 7.23 shows a communication diagram that models the ATM executing a BalanceInquiry. Objects are modeled in the UML as rectangles containing names in the form objectName : ClassName. In this example, which involves only one object of each type, we disregard the object name and list only a colon followed by the class name. [Note: Specifying the name of each object in a communication diagram is recommended when modeling multiple objects of the same type.] Communicating objects are connected with solid lines, and messages are passed between objects along these lines in the direction shown by arrows. The name of the message, which appears next to the arrow, is the name of an operation (i.e., a method in Java) belonging to the receiving object—think of the name as a “service” that the receiving object provides to sending objects (its “clients”).

Fig. 7.23. Communication diagram of the ATM executing a balance inquiry.

Image

The solid filled arrow in Fig. 7.23 represents a message—or synchronous call—in the UML and a method call in Java. This arrow indicates that the flow of control is from the sending object (the ATM) to the receiving object (a BalanceInquiry). Since this is a synchronous call, the sending object may not send another message, or do anything at all, until the receiving object processes the message and returns control to the sending object. The sender just waits. For example, in Fig. 7.23, the ATM calls method execute of a BalanceInquiry and may not send another message until execute has finished and returns control to the ATM. [Note: If this were an asynchronous call, represented by a stick arrowhead, the sending object would not have to wait for the receiving object to return control—it would continue sending additional messages immediately following the asynchronous call. Asynchronous calls are implemented in Java using a technique called multithreading, which is discussed in Chapter 18.]

Sequence of Messages in a Communication Diagram

Figure 7.24 shows a communication diagram that models the interactions among objects in the system when an object of class BalanceInquiry executes. We assume that the object’s accountNumber attribute contains the account number of the current user. The collaborations in Fig. 7.24 begin after the ATM sends an execute message to a BalanceInquiry (i.e., the interaction modeled in Fig. 7.23). The number to the left of a message name indicates the order in which the message is passed. The sequence of messages in a communication diagram progresses in numerical order from least to greatest. In this diagram, the numbering starts with message 1 and ends with message 3. The BalanceInquiry first sends a getAvailableBalance message to the BankDatabase (message 1), then sends a getTotalBalance message to the BankDatabase (message 2). Within the parentheses following a message name, we can specify a comma-separated list of the names of the parameters sent with the message (i.e., arguments in a Java method call)—the BalanceInquiry passes attribute accountNumber with its messages to the BankDatabase to indicate which Account’s balance information to retrieve. Recall from Fig. 6.27 that operations getAvailableBalance and getTotalBalance of class BankDatabase each require a parameter to identify an account. The BalanceInquiry next displays the availableBalance and the totalBalance to the user by passing a displayMessage message to the Screen (message 3) that includes a parameter indicating the message to be displayed.

Fig. 7.24. Communication diagram for executing a balance inquiry.

Image

Note that Fig. 7.24 models two additional messages passing from the BankDatabase to an Account (message 1.1 and message 2.1). To provide the ATM with the two balances of the user’s Account (as requested by messages 1 and 2), the BankDatabase must pass a getAvailableBalance and a getTotalBalance message to the user’s Account. Such messages passed within the handling of another message are called nested messages. The UML recommends using a decimal numbering scheme to indicate nested messages. For example, message 1.1 is the first message nested in message 1—the BankDatabase passes a getAvailableBalance message during BankDatabase’s processing of a message by the same name. [Note: If the BankDatabase needed to pass a second nested message while processing message 1, the second message would be numbered 1.2.] A message may be passed only when all the nested messages from the previous message have been passed. For example, the BalanceInquiry passes message 3 only after messages 2 and 2.1 have been passed, in that order.

The nested numbering scheme used in communication diagrams helps clarify precisely when and in what context each message is passed. For example, if we numbered the messages in Fig. 7.24 using a flat numbering scheme (i.e., 1, 2, 3, 4, 5), someone looking at the diagram might not be able to determine that BankDatabase passes the getAvailableBalance message (message 1.1) to an Account during the BankDatabase’s processing of message 1, as opposed to after completing the processing of message 1. The nested decimal numbers make it clear that the second getAvailableBalance message (message 1.1) is passed to an Account within the handling of the first getAvailableBalance message (message 1) by the BankDatabase.

Sequence Diagrams

Communication diagrams emphasize the participants in collaborations, but model their timing a bit awkwardly. A sequence diagram helps model the timing of collaborations more clearly. Figure 7.25 shows a sequence diagram modeling the sequence of interactions that occur when a Withdrawal executes. The dotted line extending down from an object’s rectangle is that object’s lifeline, which represents the progression of time. Actions occur along an object’s lifeline in chronological order from top to bottom—an action near the top happens before one near the bottom.

Fig. 7.25. Sequence diagram that models a Withdrawal executing.

Image

Message passing in sequence diagrams is similar to message passing in communication diagrams. A solid arrow with a filled arrowhead extending from the sending object to the receiving object represents a message between two objects. The arrowhead points to an activation on the receiving object’s lifeline. An activation, shown as a thin vertical rectangle, indicates that an object is executing. When an object returns control, a return message, represented as a dashed line with a stick arrowhead, extends from the activation of the object returning control to the activation of the object that initially sent the message. To eliminate clutter, we omit the return-message arrows—the UML allows this practice to make diagrams more readable. Like communication diagrams, sequence diagrams can indicate message parameters between the parentheses following a message name.

The sequence of messages in Fig. 7.25 begins when a Withdrawal prompts the user to choose a withdrawal amount by sending a displayMessage message to the Screen. The Withdrawal then sends a getInput message to the Keypad, which obtains input from the user. We have already modeled the control logic involved in a Withdrawal in the activity diagram of Fig. 5.22, so we do not show this logic in the sequence diagram of Fig. 7.25. Instead, we model the best-case scenario in which the balance of the user’s account is greater than or equal to the chosen withdrawal amount, and the cash dispenser contains a sufficient amount of cash to satisfy the request. For information on how to model control logic in a sequence diagram, please refer to the web resources and recommended readings listed at the end of Section 2.8.

After obtaining a withdrawal amount, the Withdrawal sends a getAvailableBalance message to the BankDatabase, which in turn sends a getAvailableBalance message to the user’s Account. Assuming that the user’s account has enough money available to permit the transaction, the Withdrawal next sends an isSufficientCashAvailable message to the CashDispenser. Assuming that there is enough cash available, the Withdrawal decreases the balance of the user’s account (i.e., both the totalBalance and the availableBalance) by sending a debit message to the BankDatabase. The BankDatabase responds by sending a debit message to the user’s Account. Finally, the Withdrawal sends a dispenseCash message to the CashDispenser and a displayMessage message to the Screen, telling the user to remove the cash from the machine.

We have identified the collaborations among objects in the ATM system and modeled some of these collaborations using UML interaction diagrams—both communication diagrams and sequence diagrams. In the next Software Engineering Case Study section (Section 8.18), we enhance the structure of our model to complete a preliminary object-oriented design, then we begin implementing the ATM system in Java.

Software Engineering Case Study Self-Review Exercises

7.1 A(n) ____________ consists of an object of one class sending a message to an object of another class.

a. association

b. aggregation

c. collaboration

d. composition

7.2 Which form of interaction diagram emphasizes what collaborations occur? Which form emphasizes when collaborations occur?

7.3

Create a sequence diagram that models the interactions among objects in the ATM system that occur when a Deposit executes successfully, and explain the sequence of messages modeled by the diagram.

Answers to Software Engineering Case Study Self-Review Exercises

7.1 c.

7.2 Communication diagrams emphasize what collaborations occur. Sequence diagrams emphasize when collaborations occur.

7.3 Figure 7.26 presents a sequence diagram that models the interactions between objects in the ATM system that occur when a Deposit executes successfully. Figure 7.26 indicates that a Deposit first sends a displayMessage message to the Screen to ask the user to enter a deposit amount. Next the Deposit sends a getInput message to the Keypad to receive input from the user. The Deposit then instructs the user to enter a deposit envelope by sending a displayMessage message to the Screen. The Deposit next sends an isEnvelopeReceived message to the DepositSlot to confirm that the deposit envelope has been received by the ATM. Finally, the Deposit increases the totalBalance attribute (but not the availableBalance attribute) of the user’s Account by sending a credit message to the BankDatabase. The BankDatabase responds by sending the same message to the user’s Account.

Fig. 7.26. Sequence diagram that models a Deposit executing.

Image

7.14 Wrap-Up

This chapter began our introduction to data structures, exploring the use of arrays to store data in and retrieve data from lists and tables of values. The chapter examples demonstrated how to declare an array, initialize an array and refer to individual elements of an array. The chapter introduced the enhanced for statement to iterate through arrays. We also illustrated how to pass arrays to methods and how to declare and manipulate multidimensional arrays. Finally, the chapter showed how to write methods that use variable-length argument lists and how to read arguments passed to a program from the command line.

We continue our coverage of data structures in Chapter 15 with generics, which provide the means to create general models of methods and classes that can be declared once, but used with many different data types. In Chapter 16, Collections, we introduce the Java Collections Framework, which uses generics to allow you to specify the exact types of objects that a particular data structure will store. Chapter 16 also introduces Java’s predefined data structures, which you can use instead of building your own. Chapter 16 discusses many data structures classes, including Vector and ArrayList, which are array-like data structures that can grow and shrink in response to a program’s changing storage requirements. The Collections API also provides class Arrays, which contains utility methods for array manipulation. Chapter 16 uses several static methods of class Arrays to perform such manipulations as sorting and searching the data in an array. You’ll be able to use some of the Arrays methods discussed in Chapter 16 after reading the current chapter, but some of the Arrays methods require knowledge of concepts presented later in the book.

We have now introduced the basic concepts of classes, objects, control statements, methods and arrays. In Chapter 8, we take a deeper look at classes and objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset