E. Arrays and ArrayLists


Objectives

In this appendix you’ll learn:

Image What arrays are.

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

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

Image To iterate through arrays with the enhanced for statement.

Image To pass arrays to methods.

Image To declare and manipulate multidimensional arrays.

Image To perform common array manipulations with the methods of class Arrays.

Image To use class ArrayList to manipulate a dynamically resizable array-like data structure.


E.1 Introduction

This appendix introduces data structures—collections of related data items. Arrays are data structures consisting of related data items of the same type. Arrays make it convenient to process related groups of values. Arrays remain the same length once they’re created, although an array variable may be reassigned such that it refers to a new array of a different length.

Although commonly used, arrays have limited capabilities. For instance, you must specify an array’s size, and if at execution time you wish to modify it, you must do so manually by creating a new array. At the end of this appendix, we introduce one of Java’s prebuilt data structures from the Java API’s collection classes. These offer greater capabilities than traditional arrays. We focus on the ArrayList collection. ArrayLists are similar to arrays but provide additional functionality, such as dynamic resizing—they automatically increase their size at execution time to accommodate additional elements.

E.2 Arrays

An array is a group of variables (called elements or components) containing values that all have the same type. Arrays are objects, so they’re 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 E.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 E.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.

Image

Fig. E.1 | A 12-element array.

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]. An indexed array name is an array-access expression, which can be used on the left side of an assignment to place a new value into an array element.


Image Common Programming Error E.1

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; otherwise, a compilation error occurs.


Let’s examine array c in Fig. E.1 more closely. The name of the array is c. Every array object knows its own length and stores it in a length instance variable. The expression c.length accesses array c’s length field to determine the length of the array. Even though the length instance variable of an array is public, it cannot be changed because it’s 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;

E.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, you specify 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 array variable c:

int[] c = new int [ 12 ];

This expression can be used to create the array shown in Fig. E.1. 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. As you’ll soon see, you can provide nondefault initial element values when you create an array.

Creating the array in Fig. E.1 can also be performed in two steps as follows:

int[] c; // declare the array variable
c = new int[ 12 ]; // create the array; assign to array variable

In the declaration, the square brackets following the type 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.

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

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

When the type of the array and the square brackets are combined at the beginning of the declaration, all the identifiers in the declaration are array variables. In this case, variables b and x refer to String arrays. For readability, we prefer to declare only one variable per declaration. The preceding declaration is equivalent to:

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

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, as in:

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


Image Common Programming Error E.2

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 element type. Similarly, in an array of a reference type, every element is a reference to an object of the array’s declared element 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.

E.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. E.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.


 1   // Fig. E.2: InitArray.java
 2   // Initializing the elements of an array to default values of zero.
 3
 4   public class InitArray
 5   {
 6      public static void main( String[] args )
 7      {
 8         int[] array; // declare array named array
 9
10         array = new int[ 10 ]; // create the array object
11
12         System.out.printf( "%s%8s ", "Index", "Value" ); // column headings
13
14         // output each array element's value                          
15         for ( int counter = 0; counter < array.length; counter++ )    
16            System.out.printf( "%5d%8d ", counter, array[ counter ] );
17      } // end main
18   } // end class InitArray


Index  Value
    0      0
    1      0
    2      0
    3      0
    4      0
    5      0
    6      0
    7      0
    8      0
    9      0


Fig. E.2 | Initializing the elements of an array to default values of zero.

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]). 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

You can create an array and initialize its elements with an array initializer—a comma-separated list of expressions (called an initializer list) enclosed in braces. In this case, the array length is determined by the number of elements in the initializer list. For example,

int[] n = { 10, 20, 30, 40, 50 };

creates a five-element array with index values 04. Element n[0] is initialized to 10, n[1] is initialized to 20, and so on. 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. E.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. E.2 (lines 15–16).


 1   // Fig. E.3: InitArray.java
 2   // Initializing the elements of an array with an array initializer.
 3
 4   public class InitArray
 5   {
 6      public static void main( String[] args )
 7      {
 8         // initializer list specifies the value for each element 
 9         int[] array = { 32, 27, 64, 18, 95, 14, 90, 70, 60, 37 };
10
11         System.out.printf( "%s%8s ", "Index", "Value" ); // column headings
12
13         // output each array element's value
14         for ( int counter = 0; counter < array.length; counter++ )
15            System.out.printf( "%5d%8d ", counter, array[ counter ] );
16      } // end main
17   } // end class InitArray


Index  Value
    0     32
    1     27
    2     64
    3     18
    4     95
    5     14
    6     90
    7     70
    8     60
    9     37


Fig. E.3 | Initializing the elements of an array with an array initializer.

Calculating the Values to Store in an Array

The application in Fig. E.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.


 1   // Fig. E.4: InitArray.java
 2   // Calculating the values to be placed into the elements of an array.
 3
 4   public class InitArray
 5   {
 6      public static void main( String[] args )
 7      {
 8         final int ARRAY_LENGTH = 10; // declare constant      
 9         int[] array = new int[ ARRAY_LENGTH ]; // create array
10
11         // calculate value for each array element
12         for ( int counter = 0; counter < array.length; counter++ )
13            array[ counter ] = 2 + 2 * counter;
14
15         System.out.printf( "%s%8s ", "Index", "Value" ); // column headings
16
17         // output each array element's value
18         for ( int counter = 0; counter < array.length; counter++ )
19            System.out.printf( "%5d%8d ", counter, array[ counter ] );
20      } // end main
21   } // end class InitArray


Index  Value
    0      2
    1      4
    2      6
    3      8
    4     10
    5     12
    6     14
    7     16
    8     18
    9     20


Fig. E.4 | Calculating the values to be placed into the elements of an array.

Line 8 uses the modifier final to declare the constant variable ARRAY_LENGTH with the value 10. Constant variables must be initialized before they’re used and cannot be modified thereafter. If you attempt to modify a final variable after it’s initialized in its declaration, the compiler issues an error message like

cannot assign a value to final variable variableName

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

variable variableName might not have been initialized


Image Good Programming Practice E.1

Constant variables also are called named constants. They 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 its context.


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. They include 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. E.5) 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] the number of grades in the range 70–79 and array[10] the number of 100 grades.


 1   // Fig. E.5: BarChart.java
 2   // Bar chart printing program.
 3
 4   public class BarChart
 5   {
 6      public static void main( String[] args )
 7      {
 8         int[] array = { 0, 0, 0, 0, 0, 0, 1, 2, 4, 2, 1 };
 9
10         System.out.println( "Grade distribution:" );
11
12         // for each array element, output a bar of the chart
13         for ( int counter = 0; counter < array.length; counter++ )
14         {
15            // output bar label ( "00-09: ", ..., "90-99: ", "100: " )
16            if ( counter == 10 )
17               System.out.printf( "%5d: ", 100 );
18            else
19               System.out.printf( "%02d-%02d: ",
20                  counter * 10, counter * 10 + 9 );
21
22            // print bar of asterisks                               
23            for ( int stars = 0; stars < array[ counter ]; stars++ )
24               System.out.print( "*" );                             
25
26            System.out.println(); // start a new line of output
27         } // end outer for
28      } // end main
29   } // end class BarChart


Grade distribution:
00-09:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
  100: *


Fig. E.5 | Bar chart printing program.

The application reads the numbers from the array and graphs the information as a bar chart. It 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, no students received a grade below 60, so array[0]array[5] contain zeroes, and no asterisks are displayed next to the first six grade ranges. In line 19, the format specifier %02d indicates that an int value should be formatted as a field of two digits. The 0 flag in the format specifier displays a leading 0 for values with fewer digits than the field width (2).

Using the Elements of an Array as Counters

Sometimes, programs use counter variables to summarize data, such as the results of a survey. Figure E.6 uses the array frequency (line 10) to count the occurrences of each side of the die that’s rolled 6,000,000 times. 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’s 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. In line 14, the calculation inside the square brackets evaluates first to determine which element of the array to increment, then the ++ operator adds one to that element. Lines 19–20 loop through array frequency to output the results.


 1   // Fig. E.6: RollDie.java
 2   // Die-rolling program using arrays instead of switch.
 3   import java.util.Random;
 4
 5   public class RollDie
 6   {
 7      public static void main( String[] args )
 8      {
 9         Random randomNumbers = new Random(); // random number generator
10         int[] frequency = new int[ 7 ]; // array of frequency counters
11
12         // roll die 6,000,000 times; use die value as frequency index
13         for ( int roll = 1; roll <= 6000000; roll++ )
14            ++frequency[ 1 + randomNumbers.nextInt( 6 ) ];
15
16         System.out.printf( "%s%10s ", "Face", "Frequency" );
17
18         // output each array element's value
19         for ( int face = 1; face < frequency.length; face++ )
20            System.out.printf( "%4d%10d ", face, frequency[ face ] );
21      } // end main
22   } // end class RollDie


Face Frequency
   1    999690
   2    999512
   3   1000575
   4    999815
   5    999781
   6   1000627


Fig. E.6 | Die-rolling program using arrays instead of switch.

Using Arrays to Analyze Survey Results

Our next example uses arrays to summarize data collected in a survey. Consider the following problem statement:

Twenty students were asked to rate on a scale of 1 to 5 the quality of the food in the student cafeteria, with 1 being “awful” and 5 being “excellent.” Place the 20 responses in an integer array and determine the frequency of each rating.

This is a typical array-processing application (Fig. E.7). We wish to summarize the number of responses of each type (that is, 1–5). Array responses (lines 9–10) is a 20-element integer array containing the students' survey responses. The last value in the array is intentionally an incorrect response (14). When a Java program executes, array element indices are checked for validity—all indices must be greater than or equal to 0 and less than the length of the array. Any attempt to access an element outside that range of indices results in a runtime error that’s known as an ArrayIndexOutOfBoundsException. At the end of this section, we’ll discuss the invalid response value, demonstrate array bounds checking and introduce Java’s exception-handling mechanism, which can be used to detect and handle an ArrayIndexOutOfBoundsException.


 1   // Fig. E.7: StudentPoll.java
 2   // Poll analysis program.
 3
 4   public class StudentPoll
 5   {
 6      public static void main( String[] args )
 7      {
 8         // student response array (more typically, input at runtime)
 9         int[] responses = { 1, 2, 5, 4, 3, 5, 2, 1, 3, 3, 1, 4, 3, 3, 3,
10            2, 3, 3, 2, 14 };
11         int[] frequency = new int[ 6 ]; // array of frequency counters
12
13         // for each answer, select responses element and use that value
14         // as frequency index to determine element to increment        
15         for ( int answer = 0; answer < responses.length; answer++ )    
16         {                                                              
17            try                                                         
18            {                                                           
19               ++frequency[ responses[ answer ] ];                      
20            } // end try                                                
21            catch ( ArrayIndexOutOfBoundsException e )                  
22            {                                                           
23               System.out.println( e );                                 
24               System.out.printf( " responses[%d] = %d ",            
25                  answer, responses[ answer ] );                        
26            } // end catch                                              
27         } // end for                                                   
28
29         System.out.printf( "%s%10s ", "Rating", "Frequency" );
30
31         // output each array element's value
32         for ( int rating = 1; rating < frequency.length; rating++ )
33            System.out.printf( "%6d%10d ", rating, frequency[ rating ] );
34      } // end main
35   } // end class StudentPoll


java.lang.ArrayIndexOutOfBoundsException: 14
   responses[19] = 14

Rating Frequency
     1         3
     2         4
     3         8
     4         2
     5         2


Fig. E.7 | Poll analysis program.

The frequency Array

We use the six-element array frequency (line 11) to count the number of occurrences of each response. Each element is used as a counter for one of the possible types of survey responses—frequency[1] counts the number of students who rated the food as 1, frequency[2] counts the number of students who rated the food as 2, and so on.

Summarizing the Results

The for statement (lines 15–27) reads the responses from the array responses one at a time and increments one of the counters frequency[1] to frequency[5]; we ignore frequency[0] because the survey responses are limited to the range 1–5. The key statement in the loop appears in line 19. This statement increments the appropriate frequency counter as determined by the value of responses[answer].

Let’s step through the first few iterations of the for statement:

• When the counter answer is 0, responses[answer] is the value of responses[0] (that is, 1—see line 9). In this case, frequency[responses[answer]] is interpreted as frequency[1], and the counter frequency[1] is incremented by one. To evaluate the expression, we begin with the value in the innermost set of brackets (answer, currently 0). The value of answer is plugged into the expression, and the next set of brackets (responses[answer]) is evaluated. That value is used as the index for the frequency array to determine which counter to increment (in this case, frequency[1]).

• The next time through the loop answer is 1, responses[answer] is the value of responses[1] (that is, 2—see line 9), so frequency[responses[answer]] is interpreted as frequency[2], causing frequency[2] to be incremented.

• When answer is 2, responses[answer] is the value of responses[2] (that is, 5—see line 9), so frequency[responses[answer]] is interpreted as frequency[5], causing frequency[5] to be incremented, and so on.

Regardless of the number of responses processed in the survey, only a six-element array (in which we ignore element zero) is required to summarize the results, because all the correct response values are between 1 and 5, and the index values for a six-element array are 0–5. In the program’s output, the Frequency column summarizes only 19 of the 20 values in the responses array—the last element of the array responses contains an incorrect response that was not counted.

Exception Handling: Processing the Incorrect Response

An exception indicates a problem that occurs while a program executes. The name “exception” suggests that the problem occurs infrequently—if the “rule” is that a statement normally executes correctly, then the problem represents the “exception to the rule.” Exception handling enables you to create fault-tolerant programs that can resolve (or handle) exceptions. In many cases, this allows a program to continue executing as if no problems were encountered. For example, the StudentPoll application still displays results (Fig. E.7), even though one of the responses was out of range. More severe problems might prevent a program from continuing normal execution, instead requiring the program to notify the user of the problem, then terminate. When the JVM or a method detects a problem, such as an invalid array index or an invalid method argument, it throws an exception—that is, an exception occurs.

The try Statement

To handle an exception, place any code that might throw an exception in a try statement (lines 17–26). The try block (lines 17–20) contains the code that might throw an exception, and the catch block (lines 21–26) contains the code that handles the exception if one occurs. You can have many catch blocks to handle different types of exceptions that might be thrown in the corresponding try block. When line 19 correctly increments an element of the frequency array, lines 21–26 are ignored. The braces that delimit the bodies of the try and catch blocks are required.

Executing the catch Block

When the program encounters the value 14 in the responses array, it attempts to add 1 to frequency[14], which is outside the bounds of the array—the frequency array has only six elements. Because array bounds checking is performed at execution time, the JVM generates an exception—specifically line 19 throws an ArrayIndexOutOfBoundsException to notify the program of this problem. At this point the try block terminates and the catch block begins executing—if you declared any variables in the try block, they’re now out of scope and are not accessible in the catch block.

The catch block declares a type (IndexOutOfRangeException) and an exception parameter (e). The catch block can handle exceptions of the specified type. Inside the catch block, you can use the parameter’s identifier to interact with a caught exception object.


Image Error-Prevention Tip E.1

When writing code to access an array element, ensure that the array index remains greater than or equal to 0 and less than the length of the array. This helps prevent ArrayIndexOutOfBoundsException in your program.


toString Method of the Exception Parameter

When lines 21–26 catch the exception, the program displays a message indicating the problem that occurred. Line 23 implicitly calls the exception object’s toString method to get the error message that is stored in the exception object and display it. Once the message is displayed in this example, the exception is considered handled and the program continues with the next statement after the catch block’s closing brace. In this example, the end of the for statement is reached (line 27), so the program continues with the increment of the control variable in line 15. We use exception handling again in Appendix F, and Appendix H presents a deeper look at exception handling.

E.5 Case Study: Card Shuffling and Dealing Simulation

The examples in the appendix thus far have used arrays containing elements of primitive types. Recall from Section E.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 reference-type 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. E.8), 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. E.9), which creates a deck of 52 playing cards in which each element is a Card object. We then build a test application (Fig. E.10) that demonstrates class DeckOfCards’s card-shuffling and dealing capabilities.

Class Card

Class Card (Fig. E.8) 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. 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. E.8.


 1   // Fig. E.8: Card.java
 2   // Card class represents a playing card.
 3
 4   public class Card
 5   {
 6      private String face; // face of card ("Ace", "Deuce", ...)
 7      private String suit; // suit of card ("Hearts", "Diamonds", ...)
 8
 9      // two-argument constructor initializes card's face and suit
10      public Card( String cardFace, String cardSuit )
11      {
12         face = cardFace; // initialize face of card
13         suit = cardSuit; // initialize suit of card
14      } // end two-argument Card constructor
15
16      // return String representation of Card
17      public String toString()               
18      {                                      
19         return face + " of " + suit;        
20      } // end method toString               
21   } // end class Card


Fig. E.8 | Card class represents a playing card.

Class DeckOfCards

Class DeckOfCards (Fig. E.9) declares as an instance variable a Card array named deck (line 7). An array of a reference type is declared like any other array. 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).


 1   // Fig. E.9: DeckOfCards.java
 2   // DeckOfCards class represents a deck of playing cards.
 3   import java.util.Random;
 4
 5   public class DeckOfCards
 6   {
 7      private Card[] deck; // array of Card objects
 8      private int currentCard; // index of next Card to be dealt (0-51)
 9      private static final int NUMBER_OF_CARDS = 52; // constant # of Cards
10      // random number generator
11      private static final Random randomNumbers = new Random();
12
13      // constructor fills deck of Cards
14      public DeckOfCards()
15      {
16         String[] faces = { "Ace", "Deuce", "Three", "Four", "Five", "Six",
17            "Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };    
18         String[] suits = { "Hearts", "Diamonds", "Clubs", "Spades" };     
19
20         deck = new Card[ NUMBER_OF_CARDS ]; // create array of Card objects
21         currentCard = 0; // set currentCard so first Card dealt is deck[ 0 ]
22
23         // populate deck with Card objects                         
24         for ( int count = 0; count < deck.length; count++ )        
25            deck[ count ] =                                         
26               new Card( faces[ count % 13 ], suits[ count / 13 ] );
27      } // end DeckOfCards constructor
28
29      // shuffle deck of Cards with one-pass algorithm
30      public void shuffle()
31      {
32         // after shuffling, dealing should start at deck[ 0 ] again
33         currentCard = 0; // reinitialize currentCard
34
35         // for each Card, pick another random Card (0-51) and swap them
36         for ( int first = 0; first < deck.length; first++ )
37         {
38            // select a random number between 0 and 51
39            int second = randomNumbers.nextInt( NUMBER_OF_CARDS );
40
41            // swap current Card with randomly selected Card
42            Card temp = deck[ first ];     
43            deck[ first ] = deck[ second ];
44            deck[ second ] = temp;         
45         } // end for
46      } // end method shuffle
47
48      // deal one Card
49      public Card dealCard()
50      {
51         // determine whether Cards remain to be dealt
52         if ( currentCard < deck.length )
53            return deck[ currentCard++ ]; // return current Card in array
54         else
55            return null; // return null to indicate that all Cards were dealt
56      } // end method dealCard
57   } // end class DeckOfCards


Fig. E.9 | DeckOfCards class represents a deck of playing cards.

DeckOfCards Constructor

The class’s constructor instantiates array deck (line 20) with NUMBER_OF_CARDS (52) elements that are all null by default. Lines 24–26 fill the deck with Cards. The loop 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 array deck). Each Card is instantiated and initialized with a String from the faces array (which contains "Ace" through "King") and a String from the suits array (which contains "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 16–17), and the calculation count / 13 always results in a value from 0 to 3 (the four indices of the suits array in line 18). 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").

DeckOfCards Method shuffle

Method shuffle (lines 30–46) shuffles the Cards in the deck. The method loops through all 52 Cards. For each Card, a number between 0 and 51 is picked randomly to select another Card, then the current Card and the randomly selected Card are swapped in the array. This exchange is performed by the 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!

[Note: It’s recommended that you use a so-called unbiased shuffling algorithm for real card games. Such an algorithm ensures that all possible shuffled card sequences are equally likely to occur. A popular unbiased shuffling algorithm is the Fisher-Yates algorithm.]

DeckOfCards Method dealCard

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 array’s length. 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.

Shuffling and Dealing Cards

Figure E.10 demonstrates class DeckOfCards (Fig. E.9). Line 9 creates a DeckOfCards object named myDeckOfCards. 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. Lines 13–20 deal all 52 Cards and print them in four columns of 13 Cards each. Line 16 deals one Card object by invoking myDeckOfCards’s dealCard method, then displays the Card left justified in a field of 19 characters. When a Card is output as a String, the Card’s toString method (lines 17–20 of Fig. E.8) is implicitly invoked. Lines 18–19 (Fig. E.10) start a new line after every four Cards.


 1   // Fig. E.10: DeckOfCardsTest.java
 2   // Card shuffling and dealing.
 3
 4   public class DeckOfCardsTest
 5   {
 6      // execute application
 7      public static void main( String[] args )
 8      {
 9         DeckOfCards myDeckOfCards = new DeckOfCards();
10         myDeckOfCards.shuffle(); // place Cards in random order
11
12         // print all 52 Cards in the order in which they are dealt
13         for ( int i = 1; i <= 52; i++ )
14         {
15            // deal and display a Card
16            System.out.printf( "%-19s", myDeckOfCards.dealCard() );
17
18            if ( i % 4 == 0 ) // output a newline after every fourth card
19               System.out.println();
20         } // end for
21      } // end main
22   } // end class DeckOfCardsTest


Six of Spades      Eight of Spades    Six of Clubs       Nine of Hearts
Queen of Hearts    Seven of Clubs     Nine of Spades     King of Hearts
Three of Diamonds  Deuce of Clubs     Ace of Hearts      Ten of Spades
Four of Spades     Ace of Clubs       Seven of Diamonds  Four of Hearts
Three of Clubs     Deuce of Hearts    Five of Spades     Jack of Diamonds
King of Clubs      Ten of Hearts      Three of Hearts    Six of Diamonds
Queen of Clubs     Eight of Diamonds  Deuce of Diamonds  Ten of Diamonds
Three of Spades    King of Diamonds   Nine of Clubs      Six of Hearts
Ace of Spades      Four of Diamonds   Seven of Hearts    Eight of Clubs
Deuce of Spades    Eight of Hearts    Five of Hearts     Queen of Spades
Jack of Hearts     Seven of Spades    Four of Clubs      Nine of Diamonds
Ace of Diamonds    Queen of Diamonds  Five of Clubs      King of Spades
Five of Diamonds   Ten of Clubs       Jack of Spades     Jack of Clubs


Fig. E.10 | Card shuffling and dealing.

E.6 Enhanced for Statement

The enhanced for statement iterates through the elements of an array without using a counter, thus avoiding the possibility of “stepping outside” the array. We show how to use the enhanced for statement with the Java API’s prebuilt data structures (called collections) in Section E.12. The syntax of an enhanced for statement is:

for ( parameter : arrayName )
   statement

where parameter has 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 element values in the array on successive iterations of the loop.

Figure E.11 uses the enhanced for statement (lines 12–13) to sum the integers in an array of student grades. The enhanced for’s parameter is of type 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 statement’s 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 statement, except that counter cannot be accessed in the body of the enhanced for statement:

for ( int counter = 0; counter < array.length; counter++ )
   total += array[ counter ];


 1   // Fig. E.11: EnhancedForTest.java
 2   // Using the enhanced for statement to total integers in an array.
 3
 4   public class EnhancedForTest
 5   {
 6      public static void main( String[] args )
 7      {
 8         int[] array = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
 9         int total = 0;
10
11         // add each element's value to total
12         for ( int number : array )          
13            total += number;                 
14
15        System.out.printf( "Total of array elements: %d ", total );
16      } // end main
17   } // end class EnhancedForTest


Total of array elements: 849


Fig. E.11 | Using the enhanced for statement to total integers in an array.

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 appendix), use the counter-controlled for statement.

E.7 Passing Arrays to Methods

This section demonstrates how to pass arrays and individual array elements as arguments 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 double[ 24 ];

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( double[] b )

indicating that modifyArray receives the reference of a double 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 E.12 demonstrates the difference between passing an entire array and passing a primitive-type array element to a method. Notice that main invokes static methods modifyArray (line 19) and modifyElement (line 30) directly. Recall from Section D.4 that a static method of a class can invoke other static methods of the same class directly.


 1   // Fig. E.12: PassArray.java
 2   // Passing arrays and individual array elements to methods.
 3
 4   public class PassArray
 5   {
 6      // main creates array and calls modifyArray and modifyElement
 7      public static void main( String[] args )
 8      {
 9         int[] array = { 1, 2, 3, 4, 5 };
10
11         System.out.println(
12            "Effects of passing reference to entire array: " +
13            "The values of the original array are:" );
14
15         // output original array elements
16         for ( int value : array )
17            System.out.printf( " %d", value );
18
19         modifyArray( array ); // pass array reference
20         System.out.println( " The values of the modified array are:" );
21
22         // output modified array elements
23         for ( int value : array )
24            System.out.printf( " %d", value );
25
26         System.out.printf(
27            " Effects of passing array element value: " +
28            "array[3] before modifyElement: %d ", array[ 3 ] );
29
30         modifyElement( array[ 3 ] ); // attempt to modify array[ 3 ]
31         System.out.printf(
32            "array[3] after modifyElement: %d ", array[ 3 ] );
33      } // end main
34
35      // multiply each element of an array by 2                     
36      public static void modifyArray( int[] array2 )                
37      {                                                             
38         for ( int counter = 0; counter < array2.length; counter++ )
39            array2[ counter ] *= 2;                                 
40      } // end method modifyArray                                   
41
42      // multiply argument by 2                                  
43      public static void modifyElement( int element )            
44      {                                                          
45         element *= 2;                                           
46         System.out.printf(                                      
47            "Value of element in modifyElement: %d ", element );
48      } // end method modifyElement                              
49   } // end class PassArray


Effects of passing reference to entire array:
The values of the original array are:
   1   2   3   4   5

The values of the modified array are:
   2   4   6   8   10

Effects of passing array element value:
array[3] before modifyElement: 8
Value of element in modifyElement: 16
array[3] after modifyElement: 8


Fig. E.12 | Passing arrays and individual array elements to methods.

The enhanced for statement at lines 16–17 outputs the five int elements of array. 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, lines 23–24 output the five elements of array again. As the output shows, method modifyArray doubled the value of each element. We could not use the enhanced for statement in lines 38–39 because we’re modifying the array’s elements.

Figure E.12 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] before invoking method modifyElement. Remember that the value of this element is now 8 after it was modified in the call to modifyArray. 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’re declared completes execution, the method parameter element is destroyed when method modifyElement terminates. 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 how 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 you 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. 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. E.12 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 so that it refers to another object, only the parameter refers to the new object—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. E.12, 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 object that array references in the calling method. In Fig. E.12, 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.


Image Performance Tip E.1

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.


E.8 Case Study: Class GradeBook Using an Array to Store Grades

Previous versions of class GradeBook 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. But this would make the code to total the grades and determine the class average cumbersome, and the class would not be able to process any more than 10 grades at a time. We solve this problem by storing grades in an array.

Storing Student Grades in an Array in Class GradeBook

Class GradeBook (Fig. E.13) uses an array of ints to store several students’ grades on a single exam. This eliminates the need to repeatedly input the same set of grades. Array grades is declared as an instance variable (line 7), so each GradeBook object maintains its own set of grades. The 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. E.14) 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 grades array’s size is determined by the length of the array that’s passed 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. In our test application, we initialize an array with grade values (Fig. E.14, 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.

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 output in Fig. E.14). 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.


 1   // Fig. E.13: GradeBook.java
 2   // GradeBook class using an array to store test grades.
 3
 4   public class GradeBook
 5   {
 6      private String courseName; // name of course this GradeBook represents
 7      private int[] grades; // array of student grades
 8
 9      // two-argument constructor initializes courseName and grades array
10      public GradeBook( String name, int[] gradesArray )
11      {
12         courseName = name; // initialize courseName
13         grades = gradesArray; // store grades
14      } // end two-argument GradeBook constructor
15
16      // method to set the course name
17      public void setCourseName( String name )
18      {
19         courseName = name; // store the course name
20      } // end method setCourseName
21
22      // method to retrieve the course name
23      public String getCourseName()
24      {
25         return courseName;
26      } // end method getCourseName
27
28      // display a welcome message to the GradeBook user
29      public void displayMessage()
30      {
31         // getCourseName gets the name of the course
32         System.out.printf( "Welcome to the grade book for %s! ",
33            getCourseName() );
34      } // end method displayMessage
35
36      // perform various operations on the data
37      public void processGrades()
38      {
39         // output grades array
40         outputGrades();
41
42         // call method getAverage to calculate the average grade
43         System.out.printf( " Class average is %.2f ", getAverage() );
44
45         // call methods getMinimum and getMaximum
46         System.out.printf( "Lowest grade is %d Highest grade is %d ",
47            getMinimum(), getMaximum() );
48
49         // call outputBarChart to print grade distribution chart
50         outputBarChart();
51      } // end method processGrades
52
53      // find minimum grade
54      public int getMinimum()
55      {
56         int lowGrade = grades[ 0 ]; // assume grades[ 0 ] is smallest
57
58         // loop through grades array                             
59         for ( int grade : grades )                               
60         {                                                        
61            // if grade lower than lowGrade, assign it to lowGrade
62            if ( grade < lowGrade )                               
63               lowGrade = grade; // new lowest grade              
64         } // end for                                             
65
66         return lowGrade; // return lowest grade
67      } // end method getMinimum
68
69      // find maximum grade
70      public int getMaximum()
71      {
72         int highGrade = grades[ 0 ]; // assume grades[ 0 ] is largest
73
74         // loop through grades array
75         for ( int grade : grades )
76         {
77            // if grade greater than highGrade, assign it to highGrade
78            if ( grade > highGrade )
79               highGrade = grade; // new highest grade
80         } // end for
81
82         return highGrade; // return highest grade
83      } // end method getMaximum
84
85      // determine average grade for test
86      public double getAverage()
87      {
88         int total = 0; // initialize total
89
90         // sum grades for one student
91         for ( int grade : grades )   
92            total += grade;           
93
94         // return average of grades
95         return (double) total / grades.length;
96      } // end method getAverage
97
98      // output bar chart displaying grade distribution
99      public void outputBarChart()
100     {
101        System.out.println( "Grade distribution:" );
102
103        // stores frequency of grades in each range of 10 grades
104        int[] frequency = new int[ 11 ];
105
106        // for each grade, increment the appropriate frequency
107        for ( int grade : grades )                            
108           ++frequency[ grade / 10 ];                         
109
110        // for each grade frequency, print bar in chart
111        for ( int count = 0; count < frequency.length; count++ )
112        {
113           // output bar label ( "00-09: ", ..., "90-99: ", "100: " )
114           if ( count == 10 )
115              System.out.printf( "%5d: ", 100 );
116           else
117              System.out.printf( "%02d-%02d: ",
118                 count * 10, count * 10 + 9 );
119
120           // print bar of asterisks
121           for ( int stars = 0; stars < frequency[ count ]; stars++ )
122              System.out.print( "*" );
123
124           System.out.println(); // start a new line of output
125        } // end outer for
126     } // end method outputBarChart
127
128     // output the contents of the grades array
129     public void outputGrades()
130     {
131        System.out.println( "The grades are: " );
132
133        // output each student's grade                             
134        for ( int student = 0; student < grades.length; student++ )
135           System.out.printf( "Student %2d: %3d ",                
136              student + 1, grades[ student ] );                    
137     } // end method outputGrades
138  } // end class GradeBook


Fig. E.13 | GradeBook class using an array to store test grades.

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. The averaging calculation in line 95 uses grades.length to determine the number of grades being averaged.

Lines 46–47 in method processGrades call 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. E.5. 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 Figs. E.6 and 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. E.14) based on the values in array frequency. Like lines 23–24 of Fig. E.5, lines 121–122 of Fig. E.13 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. E.14 creates an object of class GradeBook (Fig. E.13) using the int array gradesArray (declared and initialized in line 10 of Fig. E.14). 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.


Image Software Engineering Observation E.1

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, or it can come from a network. 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.



 1   // Fig. E.14: GradeBookTest.java
 2   // GradeBookTest creates a GradeBook object using an array of grades,
 3   // then invokes method processGrades to analyze them.
 4   public class GradeBookTest
 5   {
 6      // main method begins program execution
 7      public static void main( String[] args )
 8      {
 9         // array of student grades                                      
10         int[] gradesArray = { 87, 68, 94, 100, 83, 78, 85, 91, 76, 87 };
11
12         GradeBook myGradeBook = new GradeBook(
13            "CS101 Introduction to Java Programming", gradesArray );
14         myGradeBook.displayMessage();
15         myGradeBook.processGrades();
16      } // end main
17   } // end class GradeBookTest


Welcome to the grade book for
CS101 Introduction to Java Programming!

The grades are:

Student  1:  87
Student  2:  68
Student  3:  94
Student  4: 100
Student  5:  83
Student  6:  78
Student  7:  85
Student  8:  91
Student  9:  76
Student 10:  87

Class average is 84.90
Lowest grade is 68
Highest grade is 100

Grade distribution:
00-09:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: *
70-79: **
80-89: ****
90-99: **
  100: *


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

E.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 you to specify one-dimensional arrays whose elements are also one-dimensional arrays, thus achieving the same effect. Figure E.15 illustrates a two-dimensional array named 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.

Image

Fig. E.15 | Two-dimensional array with three rows and four columns.

Every element in array a is identified in Fig. E.15 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. 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 with array initializers in declarations. A two-dimensional array b with two rows and two columns could be declared and initialized with nested array initializers as follows:

int[][] b = { { 1, 2 }, { 3, 4 } };

The initial 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) 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 = { { 1, 2 }, { 3, 4, 5 } };

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 int[ 3 ][ 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:

int[][] b = new int[ 2 ][ ];   // create 2 rows
b[ 0 ] = new int[ 5 ]; // create 5 columns for row 0
b[ 1 ] = new int[ 3 ]; // create 3 columns for row 1

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 E.16 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). Class InitArray’s main declares two arrays. The declaration of array1 (line 9) uses nested array initializers of the same length to initialize the first row 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 two elements with the values 1 and 2, respectively. The second row is initialized to one element with the value 3. The third row is initialized to three elements with the values 4, 5 and 6, respectively.


 1   // Fig. E.16: InitArray.java
 2   // Initializing two-dimensional arrays.
 3
 4   public class InitArray
 5   {
 6      // create and output two-dimensional arrays
 7      public static void main( String[] args )
 8      {
 9         int[][] array1 = { { 1, 2, 3 }, { 4, 5, 6 } };    
10         int[][] array2 = { { 1, 2 }, { 3 }, { 4, 5, 6 } };
11
12         System.out.println( "Values in array1 by row are" );
13         outputArray( array1 ); // displays array1 by row
14
15         System.out.println( " Values in array2 by row are" );
16         outputArray( array2 ); // displays array2 by row
17      } // end main
18
19      // output rows and columns of a two-dimensional array
20      public static void outputArray( int[][] array )
21      {
22         // loop through array's rows                                     
23         for ( int row = 0; row < array.length; row++ )                   
24         {                                                                
25            // loop through columns of current row                        
26            for ( int column = 0; column < array[ row ].length; column++ )
27            System.out.printf( "%d ", array[ row ][ column ] );           
28                                                                          
29            System.out.println(); // start new line of output             
30         } // end outer for                                               
31      } // end method outputArray
32   } // end class InitArray


Values in array1 by row are
1  2  3
4  5  6

Values in array2 by row are
1  2
3
4  5  6


Fig. E.16 | Initializing two-dimensional arrays.

Lines 13 and 16 call method outputArray (lines 20–31) to output the elements of array1 and array2, respectively. Method outputArray’s parameter—int[][] array—indicates 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. The inner for statement’s 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. E.15 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:

int total = 0;
for ( int row = 0; row < a.length; row++ )
{
   for ( int column = 0; column < a[ row ].length; column++ )
      total += a[ row ][ column ];
} // end outer for

These 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.

E.10 Case Study: Class GradeBook Using a Two-Dimensional Array

In Section E.8, we presented class GradeBook (Fig. E.13), 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 E.17 contains a GradeBook class 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 the grades of all the students who took a particular exam. Class GradeBookTest (Fig. E.18) passes the array as an argument to the GradeBook constructor. In this example, we use a ten-by-three array for 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 GradeBook (Fig. E.13). 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 grade bar chart for the entire semester’s student grades. Method outputGrades (lines 140–164) outputs the array in a tabular format, along with each student’s semester average.


 1   // Fig. E.17: GradeBook.java
 2   // GradeBook class using a two-dimensional array to store grades.
 3
 4   public class GradeBook
 5   {
 6      private String courseName; // name of course this grade book represents
 7      private int[][] grades; // two-dimensional array of student grades
 8
 9      // two-argument constructor initializes courseName and grades array
10      public GradeBook( String name, int[][] gradesArray )
11      {
12         courseName = name; // initialize courseName
13         grades = gradesArray; // store grades
14      } // end two-argument GradeBook constructor
15
16      // method to set the course name
17      public void setCourseName( String name )
18      {
19         courseName = name; // store the course name
20      } // end method setCourseName
21
22      // method to retrieve the course name
23      public String getCourseName()
24      {
25         return courseName;
26      } // end method getCourseName
27
28      // display a welcome message to the GradeBook user
29      public void displayMessage()
30      {
31         // getCourseName gets the name of the course
32         System.out.printf( "Welcome to the grade book for %s! ",
33            getCourseName() );
34      } // end method displayMessage
35
36      // perform various operations on the data
37      public void processGrades()
38      {
39         // output grades array
40         outputGrades();
41
42         // call methods getMinimum and getMaximum
43         System.out.printf( " %s %d %s %d ",
44            "Lowest grade in the grade book is", getMinimum(),
45            "Highest grade in the grade book is", getMaximum() );
46
47         // output grade distribution chart of all grades on all tests
48         outputBarChart();
49      } // end method processGrades
50
51      // find minimum grade
52      public int getMinimum()
53      {
54         // assume first element of grades array is smallest
55         int lowGrade = grades[ 0 ][ 0 ];
56
57         // loop through rows of grades array                       
58         for ( int[] studentGrades : grades )                       
59         {                                                          
60            // loop through columns of current row                  
61            for ( int grade : studentGrades )                       
62            {                                                       
63               // if grade less than lowGrade, assign it to lowGrade
64               if ( grade < lowGrade )                              
65                  lowGrade = grade;                                 
66            } // end inner for                                      
67         } // end outer for                                         
68
69         return lowGrade; // return lowest grade
70      } // end method getMinimum
71
72      // find maximum grade
73      public int getMaximum()
74      {
75         // assume first element of grades array is largest
76         int highGrade = grades[ 0 ][ 0 ];
77
78         // loop through rows of grades array
79         for ( int[] studentGrades : grades )
80         {
81            // loop through columns of current row
82            for ( int grade : studentGrades )
83            {
84               // if grade greater than highGrade, assign it to highGrade
85               if ( grade > highGrade )
86                  highGrade = grade;
87            } // end inner for
88         } // end outer for
89
90         return highGrade; // return highest grade
91      } // end method getMaximum
92
93      // determine average grade for particular set of grades
94      public double getAverage( int[] setOfGrades )          
95      {                                                      
96         int total = 0; // initialize total                  
97                                                             
98         // sum grades for one student                       
99         for ( int grade : setOfGrades )                     
100           total += grade;                                  
101                                                            
102        // return average of grades                         
103        return (double) total / setOfGrades.length;         
104     } // end method getAverage                             
105
106      // output bar chart displaying overall grade distribution
107      public void outputBarChart()
108      {
109         System.out.println( "Overall grade distribution:" );
110
111         // stores frequency of grades in each range of 10 grades
112         int[] frequency = new int[ 11 ];
113
114         // for each grade in GradeBook, increment the appropriate frequency
115         for ( int[] studentGrades : grades )                               
116         {                                                                  
117            for ( int grade : studentGrades )                               
118               ++frequency[ grade / 10 ];                                   
119         } // end outer for                                                 
120
121         // for each grade frequency, print bar in chart
122         for ( int count = 0; count < frequency.length; count++ )
123         {
124            // output bar label ( "00-09: ", ..., "90-99: ", "100: " )
125            if ( count == 10 )
126               System.out.printf( "%5d: ", 100 );
127            else
128               System.out.printf( "%02d-%02d: ",
129                  count * 10, count * 10 + 9 );
130
131            // print bar of asterisks
132            for ( int stars = 0; stars < frequency[ count ]; stars++ )
133               System.out.print( "*" );
134
135            System.out.println(); // start a new line of output
136         } // end outer for
137      } // end method outputBarChart
138
139      // output the contents of the grades array
140      public void outputGrades()
141      {
142         System.out.println( "The grades are: " );
143         System.out.print( "            " ); // align column heads
144
145         // create a column heading for each of the tests
146         for ( int test = 0; test < grades[ 0 ].length; test++ )
147            System.out.printf( "Test %d  ", test + 1 );
148
149         System.out.println( "Average" ); // student average column heading
150
151         // create rows/columns of text representing array grades
152         for ( int student = 0; student < grades.length; student++ )
153         {
154            System.out.printf( "Student %2d", student + 1 );
155
156            for ( int test : grades[ student ] ) // output student's grades
157               System.out.printf( "%8d", test );
158
159            // call method getAverage to calculate student's average grade;
160            // pass row of grades as the argument to getAverage
161            double average = getAverage( grades[ student ] );
162            System.out.printf( "%9.2f ", average );
163         } // end outer for
164      } // end method outputGrades
165   } // end class GradeBook


Fig. E.17 | GradeBook class using a two-dimensional array to store grades.

Methods getMinimum and getMaximum

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

Method outputBarChart (lines 107–137) is nearly identical to the one in Fig. E.13. However, to output the overall grade distribution for a whole semester, the method here uses nested enhanced for statements (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

Method outputGrades (lines 140–164) uses nested for statements to output values of the array grades and each student’s semester average. The output (Fig. E.18) 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, lines 147 and 154 output test + 1 and student + 1, respectively, to produce test and student numbers starting at 1 (see Fig. E.18). The inner for statement (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. 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

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. E.18, 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 one 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

Figure E.18 creates an object of class GradeBook (Fig. E.17) 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.


 1   // Fig. E.18: GradeBookTest.java
 2   // GradeBookTest creates GradeBook object using a two-dimensional array
 3   // of grades, then invokes method processGrades to analyze them.
 4   public class GradeBookTest
 5   {
 6      // main method begins program execution
 7      public static void main( String[] args )
 8      {
 9         // two-dimensional array of student grades
10         int[][] gradesArray = { { 87, 96, 70 },   
11                                 { 68, 87, 90 },   
12                                 { 94, 100, 90 },  
13                                 { 100, 81, 82 },  
14                                 { 83, 65, 85 },   
15                                 { 78, 87, 65 },   
16                                 { 85, 75, 83 },   
17                                 { 91, 94, 100 },  
18                                 { 76, 72, 84 },   
19                                 { 87, 93, 73 } }; 
20
21         GradeBook myGradeBook = new GradeBook(
22            "CS101 Introduction to Java Programming", gradesArray );
23         myGradeBook.displayMessage();
24         myGradeBook.processGrades();
25      } // end main
26   } // end class GradeBookTest


Welcome to the grade book for
CS101 Introduction to Java Programming!

The grades are:

            Test 1 Test 2 Test 3 Average
Student  1      87     96     70   84.33
Student  2      68     87     90   81.67
Student  3      94    100     90   94.67
Student  4     100     81     82   87.67
Student  5      83     65     85   77.67
Student  6      78     87     65   76.67
Student  7      85     75     83   81.00
Student  8      91     94    100   95.00
Student  9      76     72     84   77.33
Student 10      87     93     73   84.33

Lowest grade in the grade book is 65
Highest grade in the grade book is 100

Overall grade distribution:
00-09:
10-19:
20-29:
30-39:
40-49:
50-59:
60-69: ***
70-79: ******
80-89: ***********
90-99: *******
  100: ***


Fig. E.18 | GradeBookTest creates GradeBook object using a two-dimensional array of grades, then invokes method processGrades to analyze them.

E.11 Class Arrays

Class Arrays helps you avoid reinventing the wheel by providing static methods for common array manipulations. These methods include sort for sorting an array (i.e., arranging elements into increasing order), binarySearch for searching an array (i.e., determining whether an array contains a specific value and, if so, where the value is located), equals for comparing arrays and fill for placing values into an array. These methods are overloaded for primitive-type arrays and for arrays of objects. Our focus in this section is on using the built-in capabilities provided by the Java API.

Figure E.19 uses Arrays methods sort, binarySearch, equals and fill, and shows how to copy arrays with class System’s static arraycopy method. In main, line 11 sorts the elements of array doubleArray. The static method sort of class Arrays orders the array’s elements in ascending order by default. Overloaded versions of sort allow you to sort a specific range of elements. Lines 12–15 output the sorted array.


 1   // Fig. E.19: ArrayManipulations.java
 2   // Arrays class methods and System.arraycopy.
 3   import java.util.Arrays;
 4
 5   public class ArrayManipulations
 6   {
 7      public static void main( String[] args )
 8      {
 9         // sort doubleArray into ascending order
10         double[] doubleArray = { 8.4, 9.3, 0.2, 7.9, 3.4 };
11         Arrays.sort( doubleArray );
12         System.out.printf( " doubleArray: " );
13
14         for ( double value : doubleArray )
15            System.out.printf( "%.1f ", value );
16
17         // fill 10-element array with 7s
18         int[] filledIntArray = new int[ 10 ];
19         Arrays.fill( filledIntArray, 7 );
20         displayArray( filledIntArray, "filledIntArray" );
21
22         // copy array intArray into array intArrayCopy
23         int[] intArray = { 1, 2, 3, 4, 5, 6 };
24         int[] intArrayCopy = new int[ intArray.length ];
25         System.arraycopy( intArray, 0, intArrayCopy, 0, intArray.length );
26         displayArray( intArray, "intArray" );
27         displayArray( intArrayCopy, "intArrayCopy" );
28
29         // compare intArray and intArrayCopy for equality
30         boolean b = Arrays.equals( intArray, intArrayCopy );
31         System.out.printf( " intArray %s intArrayCopy ",
32            ( b ? "==" : "!=" ) );
33
34         // compare intArray and filledIntArray for equality
35         b = Arrays.equals( intArray, filledIntArray );
36         System.out.printf( "intArray %s filledIntArray ",
37            ( b ? "==" : "!=" ) );
38
39         // search intArray for the value 5
40         int location = Arrays.binarySearch( intArray, 5 );
41
42         if ( location >= 0 )
43            System.out.printf(
44               "Found 5 at element %d in intArray ", location );
45         else
46            System.out.println( "5 not found in intArray" );
47
48         // search intArray for the value 8763
49         location = Arrays.binarySearch( intArray, 8763 );
50
51         if ( location >= 0 )
52            System.out.printf(
53               "Found 8763 at element %d in intArray ", location );
54         else
55            System.out.println( "8763 not found in intArray" );
56      } // end main
57
58      // output values in each array
59      public static void displayArray( int[] array, String description )
60      {
61         System.out.printf( " %s: ", description );
62
63         for ( int value : array )
64            System.out.printf( "%d ", value );
65      } // end method displayArray
66   } // end class ArrayManipulations


doubleArray: 0.2 3.4 7.9 8.4 9.3
filledIntArray: 7 7 7 7 7 7 7 7 7 7
intArray: 1 2 3 4 5 6
intArrayCopy: 1 2 3 4 5 6
intArray == intArrayCopy
intArray != filledIntArray
Found 5 at element 4 in intArray
8763 not found in intArray


Fig. E.19 | Arrays class methods and System.arraycopy.

Line 19 calls static method fill of class Arrays to populate all 10 elements of filledIntArray with 7s. Overloaded versions of fill allow you to populate a specific range of elements with the same value. Line 20 calls our class’s displayArray method (declared at lines 59–65) to output the contents of filledIntArray.

Line 25 copies the elements of intArray into intArrayCopy. The first argument (intArray) passed to System method arraycopy is the array from which elements are to be copied. The second argument (0) is the index that specifies the starting point in the range of elements to copy from the array. This value can be any valid array index. The third argument (intArrayCopy) specifies the destination array that will store the copy. The fourth argument (0) specifies the index in the destination array where the first copied element should be stored. The last argument specifies the number of elements to copy from the array in the first argument. In this case, we copy all the elements in the array.

Lines 30 and 35 call static method equals of class Arrays to determine whether all the elements of two arrays are equivalent. If the arrays contain the same elements in the same order, the method returns true; otherwise, it returns false.

Lines 40 and 49 call static method binarySearch of class Arrays to perform a binary search on intArray, using the second argument (5 and 8763, respectively) as the key. If value is found, binarySearch returns the index of the element; otherwise, binarySearch returns a negative value. The negative value returned is based on the search key’s insertion point—the index where the key would be inserted in the array if we were performing an insert operation. After binarySearch determines the insertion point, it changes its sign to negative and subtracts 1 to obtain the return value. For example, in Fig. E.19, the insertion point for the value 8763 is the element with index 6 in the array. Method binarySearch changes the insertion point to –6, subtracts 1 from it and returns the value –7. Subtracting 1 from the insertion point guarantees that method binarySearch returns positive values (>= 0) if and only if the key is found. This return value is useful for inserting elements in a sorted array.


Image Common Programming Error E.3

Passing an unsorted array to binarySearch is a logic error—the value returned is undefined.


E.12 Introduction to Collections and Class ArrayList

The Java API provides several predefined data structures, called collections, used to store groups of related objects. These classes provide efficient methods that organize, store and retrieve your data without requiring knowledge of how the data is being stored. This reduces application-development time.

You’ve used arrays to store sequences of objects. Arrays do not automatically change their size at execution time to accommodate additional elements. The collection class ArrayList<T> (from package java.util) provides a convenient solution to this problem—it can dynamically change its size to accommodate more elements. The T (by convention) is a placeholder—when declaring a new ArrayList, replace it with the type of elements that you want the ArrayList to hold. This is similar to specifying the type when declaring an array, except that only nonprimitive types can be used with these collection classes. For example,

ArrayList< String > list;

declares list as an ArrayList collection that can store only Strings. Classes with this kind of placeholder that can be used with any type are called generic classes. Additional generic collection classes and generics are discussed in Appendix J. Figure E.20 shows some common methods of class ArrayList<T>.

Image

Fig. E.20 | Some methods and properties of class ArrayList<T>.

Figure E.21 demonstrates some common ArrayList capabilities. Line 10 creates a new empty ArrayList of Strings with a default initial capacity of 10 elements. The capacity indicates how many items the ArrayList can hold without growing. ArrayList is implemented using an array behind the scenes. When the ArrayList grows, it must create a larger internal array and copy each element to the new array. This is a time-consuming operation. It would be inefficient for the ArrayList to grow each time an element is added. Instead, it grows only when an element is added and the number of elements is equal to the capacity—i.e., there’s no space for the new element.


 1   // Fig. E.21: ArrayListCollection.java
 2   // Generic ArrayList<T> collection demonstration.
 3   import java.util.ArrayList;
 4
 5   public class ArrayListCollection
 6   {
 7      public static void main( String[] args )
 8      {
 9         // create a new ArrayList of Strings with an initial capacity of 10
10         ArrayList< String > items = new ArrayList< String >();
11
12         items.add( "red" ); // append an item to the list
13         items.add( 0, "yellow" ); // insert the value at index 0
14
15         // header
16         System.out.print(
17            "Display list contents with counter-controlled loop:" );
18
19         // display the colors in the list
20         for ( int i = 0; i < items.size(); i++ )
21            System.out.printf( " %s", items.get( i ) );
22
23         // display colors using foreach in the display method
24         display( items,
25            " Display list contents with enhanced for statement:" );
26
27         items.add( "green" ); // add "green" to the end of the list
28         items.add( "yellow" ); // add "yellow" to the end of the list
29         display( items, "List with two new elements:" );
30
31         items.remove( "yellow" ); // remove the first "yellow"
32         display( items, "Remove first instance of yellow:" );
33
34         items.remove( 1 ); // remove item at index 1
35         display( items, "Remove second list element (green):" );
36
37         // check if a value is in the List
38         System.out.printf( ""red" is %sin the list ",
39            items.contains( "red" ) ? "": "not " );
40
41         // display number of elements in the List
42         System.out.printf( "Size: %s ", items.size() );
43      } // end main
44
45      // display the ArrayList's elements on the console
46      public static void display( ArrayList< String > items, String header )
47      {
48         System.out.print( header ); // display header
49
50         // display each element in items
51         for ( String item : items )
52            System.out.printf( " %s", item );
53
54         System.out.println(); // display end of line
55      } // end method display
56   } // end class ArrayListCollection


Display list contents with counter-controlled loop: yellow red
Display list contents with enhanced for statement: yellow red
List with two new elements: yellow red green yellow
Remove first instance of yellow: red green yellow
Remove second list element (green): red yellow
"red" is in the list
Size: 2


Fig. E.21 | Generic ArrayList<T> collection demonstration.

The add method adds elements to the ArrayList (lines 12–13). The add method with one argument appends its argument to the end of the ArrayList. The add method with two arguments inserts a new element at the specified position. The first argument is an index. As with arrays, collection indices start at zero. The second argument is the value to insert at that index. The indices of all subsequent elements are incremented by one. Inserting an element is usually slower than adding an element to the end of the ArrayList

Lines 20–21 display the items in the ArrayList. The size method returns the number of elements currently in the ArrayList. ArrayLists method get (line 21) obtains the element at a specified index. Lines 24–25 display the elements again by invoking method display (defined at lines 46–55). Lines 27–28 add two more elements to the ArrayList, then line 29 displays the elements again to confirm that the two elements were added to the end of the collection.

The remove method is used to remove an element with a specific value (line 31). It removes only the first such element. If no such element is in the ArrayList, remove does nothing. An overloaded version of the method removes the element at the specified index (line 34). When an element is removed, the indices of all elements after the removed element decrease by one.

Line 39 uses the contains method to check if an item is in the ArrayList. The contains method returns true if the element is found in the ArrayList, and false otherwise. The method compares its argument to each element of the ArrayList in order, so using contains on a large ArrayList can be inefficient. Line 42 displays the ArrayList’s size.

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

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