Using Arrays and Collections in Java SE 7
Arrays of objects
It is important to keep a clear distinction between an object reference variable and the object itself. An array of objects uses a reference variable, such as the names variable declared below, which is a single memory locationthat contains a reference to an array object. Each element of the array is another reference which may reference a string. Initially, they are assigned a null value:
[java]
public static void main(String args[]) { String names[] = new String[5];
…
}
[/java]
The allocation of memory for this example is illustrated in the following diagram. However, we did not include the indexes for the array in the diagram. We can assume that the top element is at index 0 and the last element is at index 4:
null null null null
null
main
names
When a string is assigned to an element of the array, the array element is modified to reference that string as illustrated as follows:
[java]
names[2] = “Steve”;
[/java]
The following diagram illustrates the modification of the reference at index 2 so that it references the string:
null null
null null
“Steve”
main
names
Be careful when using arrays that might contain null values. Consider the following code sequence where we display the contents of the names array:
[java]
for(int i = 0; i < names.length; i++) { System.out.println(names[i]);
}
[/java]
When executed, we will get the following output:
null null Steve null null
Multidimensional arrays
Many applications require the use of arrays with two or more dimensions. Tabular data with rows and columns or one that uses the x/y coordinate system are good candidates for representation using a two-dimensional array. Three or more higher dimensions are not as common, but a coordinate system using an x, y, and z value would use three dimensions. In this section, we will demonstrate multidimensional arrays using integers. However, the techniques are equally applicable to arrays of objects.
An example of how a two-dimensional array is declared is shown as follows:
static final int ROWS = 2;
static final int COLS = 3;
int grades[][] = new int[ROWS][COLS];
This will create an array with 2 rows and 3 columns depicted logically, as shown in the following diagram:
0
Row Indexes
1
Column Indexes
0 1 2
Notice that the indexes start with a zero. We can initialize each element using a series of assignment statements as follows:
grades[0][0] = 0; grades[0][1] = 1; grades[0][2] = 2; grades[1][0] = 3; grades[1][1] = 4; grades[1][2] = 5;
This is a bit tedious, but it illustrates the placement of numbers into the array, as shown in the following diagram:
0
Row Indexes
1
Column Indexes
0 1 2
0
1
2
3
4
5
Nested loops are useful for working with two-dimensional arrays. For example, to display the contents of such arrays, we will use a set of nested for loops as follows:
[java]
for (int rows = 0; rows < ROWS; rows++) {
for (int cols = 0; cols < COLS; cols++) { System.out.printf("%d ", grades[rows][cols]);
} System.out.println();
}
[/java]
When executed, we get the output as follows:
0 1 2
3 4 5
Actually, Java does not support two-dimensional arrays in the strictest sense. In reality they are arrays of arrays. In languages such as C, two-dimensional arrays are stored in row-column order. This means that the two-dimensional array is mapped to a one-dimensional space where the first row is stored in the memory, followed by the second row and then the third and so forth. This is not applicable to Java.
Instead, what we actually have is a one-dimensional array of references to a series of other one-dimensional arrays. For example, we could have created the same grades array as:
[java]
grades = new int[ROWS][]; grades[0] = new int[COLS]; grades[1] = new int[COLS];
[/java]
The array is allocated in the memory, as shown in the following diagram:
0
1
2
3
4
5
main
grades
In a two-dimensional array, the rows do not necessarily have to be the same size. In the following code sequence, we create an array with different row lengths. Arrays of this type are called ragged arrays.
grades[0] = new int[4];
grades[1] = new int[2];
The memory allocation is similar to the previous example, except for the difference in array lengths, as shown in the following diagram:
main
grades
Array techniques
There are numerous techniques for working with arrays. In this section, we will examine many of these, including:
• Traversing arrays
• Comparing arrays
• Copying arrays
• Passing an array
• Using command-line arguments
We will demonstrate variations to each technique as appropriate. Passing a variable number of arguments to a method is covered in Chapter 6, Classes, Constructors, and Methods.
Traversing arrays
Traversing an array is the process of visiting each element of an array. This is frequently done starting with the first element and moving element by element until the end of the array is reached. However, it is also possibleto move backwards or to skip elements. Here, we will focus on showing how we can traverse an array from beginning to end using two different techniques:
• Using simple for loops
• Using the for-each statement
We will use the ages array, declared as follows, to illustrate how to traverse an array:
static final int SIZE = 5;
int[] ages = new int[SIZE];
In each example, we will use this code to initialize each element of the array to 5.
Using simple loops
Any simple loop can be used to traverse an array. Looping constructs are covered in more detail in Chapter 5, Looping Constructs. Here, we will use a for loop and a while loop. First, ‘let’s examine the for loop. In the following sequence, an integer variable starts at 0 and advances to the length of the array minus one:
[java]
for(int i = 0; i < ages.length; i++) {
ages[i] = 5;
}
[/java]
The equivalent while loop follows. Note the i variable is declared outside of the loop:
[java]
int i = 0;
while(i < ages.length) {
ages[i++] = 5;
}
[/java]
The for loop is generally preferable because we know the length of the array and it is simpler for these types of problems. For both examples, we used the length property of the array to control the loop. This is preferable to using a constant variable that may have been used to declare the array. Consider the following situation where we redefine the array:
[java]
int[] ages = new int[SIZE];
...
for(int i = 0; i < SIZE; i++) {
ages[i] = 5;
}
// Array redefined
int[] ages = new int[DIFFERENT_SIZE];
...
for(int i = 0; i < SIZE; i++) {
ages[i] = 5;
}
[/java]
The second for loop will not execute properly because we forgot to change the SIZE constant and may even throw an exception if the array is smaller than SIZE. If we had used the length property instead, there wouldnot have been a problem.
Notice, that the for loop, as written, declares the variable i within the for loop. This restricts access to the variable to only those statements within the for loop. In the while loop example, we declared i outside of the loopmaking it accessible inside, and outside, of the while loop. We could have rewritten the for loop to use an external i variable. However, it is considered to be better form to restrict access to a variable to only thosestatements that need access. Thus, if it is only needed inside of the loop then the for loop provides a better choice.
Using simple for statements can result in off-by-one errors (starting at the wrong beginning or ending index). For example, if the value used as the last index is larger than the size of the array minus one, then aArrayIndexOutOfBoundsException exception will be thrown.
Using the for-each statement
The for-each statement provides a more convenient method of traversing an array if we do not need explicit access to each element’s index value. The for-each parentheses’ body consists of a data type, a variable name, colon, and then an array (or collection). The statement will iterate through the array starting with the first element and ending with the last. During each iteration the variable references that array element. The following illustrates the use of this statement with the ages array. During the first iteration, number references ages[0]. During the second iteration, number references ages[1]. This continues for-each element of the array:
[java]
for(int number : ages) {
number = 5;
}
[/java]
The for-each statement makes it easy to traverse an array. However, if we need to use the index of an array element, the statement does not provide access to its value. The traditional for loop is needed to access the index.
The following table summarizes the differences between the use of the for loop and the for-each loop:
Provides access to the array element
for loop for-each loop
Provides access to the array index
Uses logical expression to control loop
Simplest
Comparing arrays
As an array variable is a reference variable, comparing array reference variables to determine equality will not always work. Here, we will examine several techniques for comparing arrays including:
• Element-by-element comparison
• Using the equality operator
• Using the equals method
• Using the deepEquals method
We will demonstrate these techniques by comparing two integer arrays. Consider the following example where two arrays, arr1 and arr2, are equivalent after we initialize them to contain the same data:
[java]
public static void main(String[] args) {
int arr1[];
int arr2[];
arr1 = new int[5];
arr2 = new int[5];
for(int i = 0; i < 5; i++) { arr1[i] = 0; arr2[i] = 0; } } [/java]
The following diagram shows how memory is allocated for both arrays:
0
0
0
0
0
0
0
0
0
0
main
arr1
arr2
Element-by-element comparison
This simple approach will compare the corresponding elements of each array to determine if the arrays are equal. It starts by assuming they are equal and assigns a true value to the areEqual variable. If any comparison is false, then the variable is assigned the value of false:
[java]
boolean areEqual = true;
for (i = 0; i < 5; i++) { if(arr1[i]!= arr2[i]) { areEqual = false; } } System.out.println(areEqual); [/java] When this sequence is executed, it will display true. This is not the best approach. Using indexes is an error prone and tedious approach. Using the equality operator
If we try to compare the two arrays using the equality operator, we find that the result of the comparison will be false:
[java]
System.out.println(arr1 == arr2); //Displays false
[/java]
This is because we are comparing arr1 and arr2 which are array reference variables and not the arrays. The variables, arr1 and arr2, reference different objects in memory. The contents of these two reference variables are different, therefore,
when they are compared to each other they are not equal. They don’t reference the same object.
Using the equals method
We can use the equals method with arrays as we can with other objects. In the following example, the output will be false even though they are equivalent. This is because the equals method, as applied to arrays, tests for object equivalency and not object value equivalency.
System.out.println(arr1.equals(arr2)); // Displays false
Object equivalency refers to the comparison of two object reference variables. If these variables reference the same object, they are considered to be equivalent. Object
value equivalency refers to the condition where two distinct objects are considered to be equivalent because their internal values are the same.
Using the deepEquals method
To compare two arrays correctly we need to use the Arrays class’ equals or deepEquals methods. The equals method performs a comparison using object identities. The deepEquals method performs a more in depth examination of the elements for value equivalency.
The following statement will display true:
System.out.println(Arrays.equals(arr1,arr2));
The deepEquals method requires an array of objects. The two-dimensional grades array, used in the Multidimensional arrays section, satisfies the requirement as it is an array of arrays, that is, an array that referencesother arrays (which are objects).
If we create a second grade array, grades2, and populate it with the same values as grades, we can use these methods to test for equality. The creation and initialization of the grades2 array follows:
[java]
int grades2[][];
grades2 = new int[ROWS][]; grades2[0] = new int[COLS]; grades2[1] = new int[COLS];
grades2[0][0] = 0; grades2[0][1] = 1; grades2[0][2] = 2;
grades2[1][0] = 3; grades2[1][1] = 4; grades2[1][2] = 5;
[/java]
If we execute the following sequence:
[java]
System.out.println(grades == grades2); System.out.println(grades.equals(grades2)); System.out.println(Arrays.equals(grades, grades2)); System.out.println(Arrays.deepEquals(grades, grades2));
[/java]
We will get the following output:
false false false true
The first three comparisons returned false because they did not adequately compare the two arrays. The fourth technique compared the arrays in depth and accurately determined their equivalency.
The following table summarizes these techniques:
Technique Comment
Element-by-element comparison This will compare arrays properly, if implemented correctly.
Using the equality operator This only works properly if the two reference variables reference the same object. Using the array’s equals method This only works properly if the two reference variables reference the same object. Using the Array’s class equals method This will work for one-dimensional arrays.
Using the Array’s class deepEquals method. This performs a deeper comparison using the object’s equals method.
Copying arrays
There are times when we need to copy one array to another. In this section, we will examine various techniques to achieve this goal. These include:
• Simple element-by-element copy
• Using the System.arraycopy method
• Using the Arrays.copyOf method
• Using the Arrays.copyOfRange method
• Using the clone method
We will demonstrate the techniques using two one-dimensional arrays as declared below:
[java]
int arr1[] = new int[5];
int arr2[] = new int[5];
We will initialize each element of arr1 to its index with the following code:
for(int i = 0; i < arr1.length; i++) { arr1[i] = i; } [/java] In this section's examples, the content of the destination array follows as a comment. We will also use the terms, shallow copy and deep copy. Shallow copy refers to when only the reference values are copied. After the copy operation, the original object has not been duplicated. In a deep copy, the reference to the object is not copied. Instead, a new copy of the object is created. We will see how some of the techniques illustrated here only perform a shallow copy which may not always be desirable.
Simple element-by-element copy
A simple technique is to use a for loop as illustrated below:
[java]
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
// 0, 1, 2, 3, 4
[/java]
This is a simple approach but you need to be careful to use the correct array indexes. This technique becomes more complicated with multidimensional arrays.
Using the System.arraycopy method
The System class’ arraycopy method will attempt to copy all, or part, of one array to another. The beginning position in each array is specified, along with the number of elements to copy.
To copy all of the elements of arr1 to arr2 we can use the following code:
System.arraycopy(arr1, 0, arr2, 0, 5);
// 0, 1, 2, 3, 4
The parameters of this method are detailed in the following table:
Parameter Description
1 The source array
2 The starting index in the source array
3 The destination array
4 The starting index in the destination array
5 The number of elements to copy
The next sequence copies the first three elements of arr1 to the last three elements of arr2:
System.arraycopy(arr1, 0, arr2, 2, 3);
// 0 0 0 1 2
We can also copy part of one array to other positions within the same array. Here we copy the first two elements to the last two elements of the arr1 array:
System.arraycopy(arr1, 0, arr1, 3, 2);
// 0 1 2 0 1
There are numerous opportunities for exceptions to occur when using this technique. If either array reference is null, a NullPointerException exception is thrown. If the array indexes are invalid, then we will get a IndexOutOfBoundsException exception.
The arraycopy method will copy the specified elements of the source array to
the corresponding element of the destination array. There are two possible results
depending on the data type of the array. They are as follows:
• If the array element type is a primitive data type, then the two arrays are effectively identical.
• If the array element type is a reference, then both arrays will be identical but they will both reference the same objects. This is usually not the effect anticipated or desired.
In the following code sequence, an attempt is made to create an identical copy of the
[java]
StringBuilder array, arr3:
StringBuilder arr3[] = new StringBuilder[4];
arr3[0] = new StringBuilder(“Pine”); arr3[1] = new StringBuilder(“Oak”); arr3[2] = new StringBuilder(“Maple”); arr3[3] = new StringBuilder(“Walnut”);
StringBuilder arr4[] = new StringBuilder[4]; System.arraycopy(arr3, 0, arr4, 0, 4);
[/java]
However, arr4 contains the same object reference variables used by arr3. The corresponding element of both arrays reference the same object. The creation of an identical array with references to distinct strings isachieved with the following code:
[java]
for (int i = 0; i < arr3.length; i++) {
arr4[i] = new StringBuilder(arr3[i]);
}
[/java]
We created a new StringBuilder object for-each element of the destination array. This approach is necessary if a deep copy is needed.
Using the Arrays.copyOf method
The Arrays class’ copyOf method will create a new array based on an existing array. The first argument of the method specifies the original array. Its second argument specifies how many elements to copy. In the following example, we create a new array based on the first three elements of arr1:
[java]
arr2 = Arrays.copyOf(arr1, 3);
// 0 1 2
[/java]
The new array can be larger than the original array as illustrated with the following code:
[java]
arr2 = Arrays.copyOf(arr1, 10);
// 0 1 2 3 4 0 0 0 0 0
[/java]
The last five elements of arr2 will be padded with zeros.
If the array is an array of objects, a copy of the original object is assigned to the new array.
Using the Arrays.copyOfRange method
The Arrays class’ copyOfRange method will create a new array based on a sub- range of elements in an existing array. The first argument of the method specifies the original array. Its second argument specifies the beginning index and the last argument specifies the ending index exclusive. In the following example, we create a new array based on the last two elements of arr1:
[java]
arr2 = Arrays.copyOfRange(arr1, 3, 5);
// 3 4
[/java]
Notice that the last argument is not a valid index for the arr1 array. This works here because the last argument is exclusive. It does not include that element.
In fact, if we specify a value such as 8 in the next example, the new array is padded with zeros:
[java]
arr2 = Arrays.copyOfRange(arr1, 3, 8);
// 3 4 0 0 0
[/java]
Using the clone method
You can also use the Object class’ clone method to create a copy of an array:
[java]
arr2 = arr1.clone();
[/java]
However, this only makes a shallow copy of the original object. With an array of primitives such as the above integer array, this is not a problem. With an array of references to objects, both arrays will reference the same objects.
The following table summarizes the copying techniques introduced in this section:
Technique Comment
Simple element-by-element copy Tedious but can implement either a shallow or deep copy
Using the System.arraycopy method Performs a shallow copy
Using the Arrays.copyOf method Performs a deep copy of the entire array Using the Arrays.copyOfRange method Performs a deep copy of part of an array Using the clone method Performs a shallow copy
Passing arrays
The advantage of passing an array to a method is that it allows us to perform the same operation against more than one array. To pass an array to a method, we use the array name in the method call and declare a reference to the passed array in
the method. This is illustrated below with a call to the displayArray method. This method simply displays the array.
[java]
displayArray(arr2);
…
private static void displayArray(int arr[]) {
for(int number : arr) { System.out.print(number + ” “);
} System.out.println();
}
[/java]
Notice that we are “passing a reference” to the arr2 array “by value”. That is, if we want, we can read and write the elements of the arr2 array in the method. However, if we modify the arr parameter, the original arr2variable is not modified.
Consider the method in the following code that attempts to change what the arr2 reference variable points to:
[java]
System.out.println(“Length of arr2: ” + arr2.length);
changeArray(arr2);
System.out.println(“Length of arr2: ” + arr2.length);
…
private static void changeArray(int arr[]) {
arr = new int[100];
System.out.println(“Length of arr: ” + arr.length);
}
[/java]
When we execute this code, we get the following output:
Length of arr2: 5
Length of arr: 100
Length of arr2: 5
The value of arr was changed but the value of arr2 was not changed. The following diagram should help clarify this behavior:
0 0
0 0
0 0
0 0
0 0
0
0
0
. . .
0
changeArray
main
arr
arr2
changeArray
main
arr
arr2