Team LiB
Previous Section Next Section

2.8. Arrays

An array is a special kind of object that holds zero or more primitive values or references. These values are held in the elements of the array, which are unnamed variables referred to by their position or index. The type of an array is characterized by its element type , and all elements of the array must be of that type.

Array elements are numbered starting with zero, and valid indexes range from zero to the number of elements minus one. The array element with index 1, for example, is the second element in the array. The number of elements in an array is its length. The length of an array is specified when the array is created, and it never changes.

The element type of an array may be any valid Java type, including array types. This means that Java supports arrays of arrays, which provide a kind of multidimensional array capability. Java does not support the matrix-style multidimensional arrays found in some languages.

2.8.1. Array Types

Array types are reference types, just as classes are. Instances of arrays are objects, just as the instances of a class are.[7] Unlike classes, array types do not have to be defined. Simply place square brackets after the element type. For example, the following code declares three variables of array type:

[7] There is a terminology difficulty when discussing arrays. Unlike with classes and their instances, we use the term "array" for both the array type and the array instance. In practice, it is usually clear from context whether a type or a value is being discussed.

byte b;                        // byte is a primitive type
byte[] arrayOfBytes;           // byte[] is an array type: array of byte
byte[][] arrayOfArrayOfBytes;  // byte[][] is another type: array of byte[]
String[] points;               // String[] is an array of String objects

The length of an array is not part of the array type. It is not possible, for example, to declare a method that expects an array of exactly four int values, for example. If a method parameter is of type int[ ], a caller can pass an array with any number (including zero) of elements.

Array types are not classes, but array instances are objects. This means that arrays inherit the methods of java.lang.Object. Arrays implement the Cloneable interface and override the clone( ) method to guarantee that an array can always be cloned and that clone( ) never throws a CloneNotSupportedException. Arrays also implement Serializable so that any array can be serialized if its element type can be serialized. Finally, all arrays have a public final int field named length that specifies the number of elements in the array.

2.8.1.1 Array type widening conversions

Since arrays extend Object and implement the Cloneable and Serializable interfaces, any array type can be widened to any of these three types. But certain array types can also be widened to other array types. If the element type of an array is a reference type T, and T is assignable to a type S, the array type T[ ] is assignable to the array type S[ ]. Note that there are no widening conversions of this sort for arrays of a given primitive type. As examples, the following lines of code show legal array widening conversions:

String[] arrayOfStrings;      // Created elsewhere
int[][] arrayOfArraysOfInt;   // Created elsewhere
// String is assignable to Object, so String[] is assignable to Object[]
Object[] oa = arrayOfStrings;
// String implements Comparable, so a String[] can be considered a Comparable[]
Comparable[] ca = arrayOfStrings;
// An int[] is an Object, so int[][] is assignable to Object[]
Object[] oa2 = arrayOfArraysOfInt;
// All arrays are cloneable, serializable Objects
Object o = arrayOfStrings;
Cloneable c = arrayOfArraysOfInt;
Serializable s = arrayOfArraysOfInt[0];

This ability to widen an array type to another array type means that the compile-time type of an array is not always the same as its runtime type. The compiler must usually insert runtime checks before any operation that stores a reference value into an array element to ensure that the runtime type of the value matches the runtime type of the array element. If the runtime check fails, an ArrayStoreException is thrown.

2.8.1.2 C compatibility syntax

As we've seen, an array type is written simply by placing brackets after the element type. For compatibility with C and C++, however, Java supports an alternative syntax in variable declarations: brackets may be placed after the name of the variable instead of, or in addition to, the element type. This applies to local variables, fields, and method parameters. For example:

// This line declares local variables of type int, int[] and int[][]
int justOne, arrayOfThem[], arrayOfArrays[][];

// These three lines declare fields of the same array type:
public String[][] aas1;   // Preferred Java syntax
public String aas2[][];   // C syntax
public String[] aas3[];   // Confusing hybrid syntax

// This method signature includes two parameters with the same type
public static double dotProduct(double[] x, double y[]) { ... }

This compatibility syntax is uncommon, and its use is strongly discouraged.

2.8.2. Creating and Initializing Arrays

To create an array value in Java, you use the new keyword, just as you do to create an object. Array types don't have constructors, but you are required to specify a length whenever you create an array. Specify the desired size of your array as a nonnegative integer between square brackets:

byte[] buffer = new byte[1024];  // Create a new array to hold 1024 bytes
String[] lines = new String[50]; // Create an array of 50 references to strings

When you create an array with this syntax, each of the array elements is automatically initialized to the same default value that is used for the fields of a class: false for boolean elements, '\u0000' for char elements, 0 for integer elements, 0.0 for floating-point elements, and null for elements of reference type.

Array creation expressions can also be used to create and initialize a multidimensional rectangular array of arrays. This syntax is somewhat more complicated and is explained later in this section.

2.8.2.1 Array initializers

To create an array and initialize its elements in a single expression, omit the array length and follow the square brackets with a comma-separated list of expressions within curly braces. The type of each expression must be assignable to the element type of the array, of course. The length of the array that is created is equal to the number of expressions. It is legal, but not necessary, to include a trailing comma following the last expression in the list. For example:

String[] greetings = new String[] { "Hello", "Hi", "Howdy" };
int[] smallPrimes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, };

Note that this syntax allows arrays to be created, initialized, and used without ever being assigned to a variable. In a sense these array creation expressions are anonymous array literals. Here are examples:

// Call a method, passing an anonymous array literal that contains two strings
String response = askQuestion("Do you want to quit?",
                               new String[] {"Yes", "No"});

// Call another method with an anonymous array (of anonymous objects)
double d = computeAreaOfTriangle(new Point[] { new Point(1,2),
                                               new Point(3,4),
                                               new Point(3,2) });

When an array initializer is part of a variable declaration, you may omit the new keyword and element type and list the desired array elements within curly braces:

String[] greetings = { "Hello", "Hi", "Howdy" };
int[] powersOfTwo = {1, 2, 4, 8, 16, 32, 64, 128};

The Java Virtual Machine architecture does not support any kind of efficient array initialization. In other words, array literals are created and initialized when the program is run, not when the program is compiled. Consider the following array literal:

int[] perfectNumbers = {6, 28};

This is compiled into Java byte codes that are equivalent to:

int[] perfectNumbers = new int[2];
perfectNumbers[0] = 6;
perfectNumbers[1] = 28;

If you want to initialize a large array, you should think twice before including the values literally in the program, since the Java compiler has to emit lots of Java byte codes to initialize the array. It may be more space-efficient to store your data in an external file and read it into the program at runtime.

The fact that Java does all array initialization at runtime has an important corollary, however. It means that the expressions in an array initializer may be computed at runtime and need not be compile-time constants. For example:

Point[] points = { circle1.getCenterPoint(  ), circle2.getCenterPoint(  ) };

2.8.3. Using Arrays

Once an array has been created, you are ready to start using it. The following sections explain basic access to the elements of an array and cover common idioms of array usage such as iterating through the elements of an array and copying an array or part of an array.

2.8.3.1 Accessing array elements

The elements of an array are variables. When an array element appears in an expression, it evaluates to the value held in the element. And when an array element appears on the left-hand side of an assignment operator, a new value is stored into that element. Unlike a normal variable, however, an array element has no name, only a number. Array elements are accessed using a square bracket notation. If a is an expression that evaluates to an array reference, you index that array and refer to a specific element with a[i], where i is an integer literal or an expression that evaluates to an int. For example:

String[] responses = new String[2];   // Create an array of two strings
responses[0] = "Yes";                 // Set the first element of the array
responses[1] = "No";                  // Set the second element of the array

// Now read these array elements
System.out.println(question + " (" + responses[0] + "/" +
                   responses[1] + " ): ");

// Both the array reference and the array index may be more complex expressions
double datum = data.getMatrix(  )[data.row(  )*data.numColumns(  ) +
                   data.column(  )];

The array index expression must be of type int, or a type that can be widened to an int: byte, short, or even char. It is obviously not legal to index an array with a boolean, float, or double value. Remember that the length field of an array is an int and that arrays may not have more than Integer.MAX_VALUE elements. Indexing an array with an expression of type long generates a commpile-time error, even if the value of that expression at runtime would be within the range of an int.

2.8.3.2 Array bounds

Remember that the first element of an array a is a[0] , the second element is a[1] and the last is a[a.length-1]. If you are accustomed to a language in which the arrays are 1-based, 0-based arrays take some getting used to.

A common bug involving arrays is use of an index that is too small (a negative index) or too large (greater than or equal to the array length). In languages like C or C++, accessing elements before the beginning or after the end of an array yields unpredictable behavior that can vary from invocation to invocation and platform to platform. Such bugs may not always be caught, and if a failure occurs, it may be at some later time. While it is just as easy to write faulty array indexing code in Java, Java guarantees predictable results by checking every array access at runtime. If an array index is too small or too large, Java throws an ArrayIndexOutOfBoundsException immediately.

2.8.3.3 Iterating arrays

It is common to write loops that iterate through each of the elements of an array in order to perform some operation on it. This is typically done with a for loop. The following code, for example, computes the sum of an array of integers:

int[] primes = { 2, 3, 5, 7, 11, 13, 17, 19 };
int sumOfPrimes = 0;
for(int i = 0; i < primes.length; i++)
    sumOfPrimes += primes[i];

The structure of this for loop is idiomatic, and you'll see it frequently.

In Java 5.0 and later, arrays can also be iterated with the for/in loop. The summing code could be rewritten succinctly as follows:

for(int p : primes) sumOfPrimes += p;

2.8.3.4 Copying arrays

All array types implement the Cloneable interface, and any array can be copied by invoking its clone( ) method. Note that a cast is required to convert the return value to the appropriate array type, but that the clone( ) method of arrays is guaranteed not to throw CloneNotSupportedException:

int[] data = { 1, 2, 3 };
int[] copy = (int[]) data.clone(  );

The clone( ) method makes a shallow copy. If the element type of the array is a reference type, only the references are copied, not the referenced objects themselves. Because the copy is shallow, any array can be cloned, even if the element type is not itself Cloneable.

Sometimes you simply want to copy elements from one existing array to another existing array. The System.arraycopy( ) method is designed to do this efficiently, and you can assume that Java VM implementations performs this method using high-speed block copy operations on the underlying hardware.

arraycopy( ) is a straightforward function that is difficult to use only because it has five arguments to remember. First pass the source array from which elements are to be copied. Second, pass the index of the start element in that array. Pass the destination array and the destination index as the third and fourth arguments. Finally, as the fifth argument, specify the number of elements to be copied.

arraycopy( ) works correctly even for overlapping copies within the same array. For example, if you've "deleted" the element at index 0 from array a and want to shift the elements between indexes 1 and n down one so that they occupy indexes 0 through n-1 you could do this:

System.arraycopy(a, 1, a, 0, n);

2.8.3.5 Array utilities

The java.util.Arrays class contains a number of static utility methods for working with arrays. Most of these methods are heavily overloaded, with versions for arrays of each primitive type and another version for arrays of objects. The sort( ) and binarySearch( ) methods are particularly useful for sorting and searching arrays. The equals( ) method allows you to compare the content of two arrays. The Arrays.toString( ) method is useful when you want to convert array content to a string, such as for debugging or logging output.

As of Java 5.0, the Arrays class includes deepEquals( ) , deepHashCode( ), and deepToString( ) methods that work correctly for multidimensional arrays.

2.8.4. Multidimensional Arrays

As we've seen, an array type is written as the element type followed by a pair of square brackets. An array of char is char[ ], and an array of arrays of char is char[ ][ ]. When the elements of an array are themselves arrays, we say that the array is multidimensional. In order to work with multidimensional arrays, you need to understand a few additional details.

Imagine that you want to use a multidimensional array to represent a multiplication table:

int[][] products;      // A multiplication table

Each of the pairs of square brackets represents one dimension, so this is a two-dimensional array. To access a single int element of this two-dimensional array, you must specify two index values, one for each dimension. Assuming that this array was actually initialized as a multiplication table, the int value stored at any given element would be the product of the two indexes. That is, products[2][4] would be 8, and products[3][7] would be 21.

To create a new multidimensional array, use the new keyword and specify the size of both dimensions of the array. For example:

int[][] products = new int[10][10];

In some languages, an array like this would be created as a single block of 100 int values. Java does not work this way. This line of code does three things:

  • Declares a variable named products to hold an array of arrays of int.

  • Creates a 10-element array to hold 10 arrays of int.

  • Creates 10 more arrays, each of which is a 10-element array of int. It assigns each of these 10 new arrays to the elements of the initial array. The default value of every int element of each of these 10 new arrays is 0.

To put this another way, the previous single line of code is equivalent to the following code:

int[][] products = new int[10][];    // An array to hold 10 int[] values
for(int i = 0; i < 10; i++)          // Loop 10 times...
    products[i] = new int[10];       // ...and create 10 arrays

The new keyword performs this additional initialization automatically for you. It works with arrays with more than two dimensions as well:

float[][][] globalTemperatureData = new float[360][180][100];

When using new with multidimensional arrays, you do not have to specify a size for all dimensions of the array, only the leftmost dimension or dimensions. For example, the following two lines are legal:

float[][][] globalTemperatureData = new float[360][][];
float[][][] globalTemperatureData = new float[360][180][];

The first line creates a single-dimensional array, where each element of the array can hold a float[ ][ ]. The second line creates a two-dimensional array, where each element of the array is a float[ ]. If you specify a size for only some of the dimensions of an array, however, those dimensions must be the leftmost ones. The following lines are not legal:

float[][][] globalTemperatureData = new float[360][][100];  // Error!
float[][][] globalTemperatureData = new float[][180][100];  // Error!

Like a one-dimensional array, a multidimensional array can be initialized using an array initializer. Simply use nested sets of curly braces to nest arrays within arrays. For example, we can declare, create, and initialize a 55 multiplication table like this:

int[][] products = { {0, 0, 0, 0, 0},
                     {0, 1, 2, 3, 4},
                     {0, 2, 4, 6, 8},
                     {0, 3, 6, 9, 12},
                     {0, 4, 8, 12, 16} };

Or, if you want to use a multidimensional array without declaring a variable, you can use the anonymous initializer syntax:

boolean response = bilingualQuestion(question, new String[][] {
                                                   { "Yes", "No" },
                                                   { "Oui", "Non" }});

When you create a multidimensional array using the new keyword, you always get a rectangular array: one in which all the array values for a given dimension have the same size. This is perfect for rectangular data structures, such as matrices. However, because multidimensional arrays are implemented as arrays of arrays in Java, instead of as a single rectangular block of elements, you are in no way constrained to use rectangular arrays. For example, since our multiplication table is symmetrical diagonally from top left to bottom right, we can represent the same information in a nonrectangular array with fewer elements:

int[][] products = { {0},
                     {0, 1},
                     {0, 2, 4},
                     {0, 3, 6, 9},
                     {0, 4, 8, 12, 16} };

When working with multidimensional arrays, you'll often find yourself using nested loops to create or initialize them. For example, you can create and initialize a large triangular multiplication table as follows:

int[][] products = new int[12][];          // An array of 12 arrays of int.
for(int row = 0; row < 12; row++) {        // For each element of that array,
    products[row] = new int[row+1];        // allocate an array of int.
    for(int col = 0; col < row+1; col++)   // For each element of the int[],
        products[row][col] = row * col;    // initialize it to the  product.
}

    Team LiB
    Previous Section Next Section