Skip to main content

Section 7.4 "Multidimensional" Arrays

If an array can truly hold any Java type, then an array element should itself be able to hold an array. To be specific, because an array is a reference type, we can declare an array to hold references to other arrays. In this section we’ll explore how to create and initialize arrays of arrays and how to work with them.

Subsection 7.4.1 Declaring Arrays of Arrays

In Section 7.1 we learned that we could declare an array variable of any Java type by following the type with a pair of square brackets ([]). Furthermore, an array with elements of a data type is created using the the new keyword followed by the type and a pair of square brackets with a number of elements.
In the following code snippet the list variable is declared with type double array, and initialized with a new double array of size 5. We follow the rules outlined above to create and initialize a new array that itself holds arrays. If double[] is the type of a double array formed by adding a pair of square brackets to the right of the type double, then an array of double arrays is obtained by adding another pair of square brackets after the double array, like double[][]. Continuing with this line of reasoning, if the expression new double[5] creates a new double array with 5 items, then the expression new double[5][3] creates a new array capable of holding 5 double arrays, each of which can hold 3 doubles. This is demonstrated in the second half of the following code snippet and illustrated in Figure 7.4.1. This same pattern of adding square brackets may be continued to form arrays of arrays of arrays, etc.
// An array of doubles
double[] list = new double[5];

// An array of arrays of doubles
double[][] matrix = new double[5][3];
Figure 7.4.1. A schematic of matrix, an array of arrays

Subsection 7.4.2 Accessing "Multidimensional" Array Elements

Using the same reasoning as in Subsection 5.6.1 we can access all elements in a "multidimensional" array using nested iteration. We use an outer loop with a loop counter to generate the indexes of the elements in the top-level array, and we use an inner loop with a counter to generate the indexes of the elements in each inner array. It is best to use the array’s .length property in the continuation test of both the outer and inner loops.
Listing 7.4.2 demonstrates these ideas. This program has two methods defined, main(…) and print2D(…). Both methods use the nested iteration approach to access array elements. In main(…) we access each element and initialize values to random doubles. In print2D(…) we access each element for printing. Pay special attention to the inner loop continuation test, j<arr2D[i].length. We are querying each inner array for its length, that is, each array at arr2D[i]. In the next section we’ll learn why we want to ask each inner array for its length, and not use the length of just the first inner array or a constant.
// NestedArrays1.java
import java.util.Random;

public class NestedArrays1 {
  public static void main(String[] args) {
    Random rnd = new Random();                // Random object
    double[][] arr2D = new double[5][3];      // 2D array

    // Fill array with random numbers
    for (int i=0; i<arr2D.length; i++) {      // Outer loop
      for (int j=0; j<arr2D[i].length; j++) { // Inner loop
        arr2D[i][j] = rnd.nextDouble();
      }
    }

    print2D( arr2D );                         // Print array
  }

  // Print an arbitrary 2D double array
  public static void print2D( double[][] arr ) {
    for (int i=0; i<arr.length; i++) {        // Outer loop
      for (int j=0; j<arr[i].length; j++) {   // Inner loop
        System.out.print( arr[i][j] + " ");   // Print adjacent
      }
      System.out.println();                   // Next line
    }
  }
}
Listing 7.4.2. NestedArrays1.java
javac NestedArrays1.java
java NestedArrays1
0.503106975940591 0.9917565242540053 0.17632812964595546 
0.7495600013315958 0.01605752178519637 0.9485705200206167
0.33889267773442544 0.47985018921768474 0.042949682032970005
0.6807634494160301 0.5147152407456702 0.312225944100627
0.7609294164182726 0.21161251517956636 0.890812892598448
Initialization of multidimensional arrays is permitted at the time of declaration using curly braces in a manner similar to initializing an array with one dimension. For multidimensional arrays the initialization structure must match the structure of the array, using nested curly braces {{…}, {…}, …}.
In Listing 7.4.3 we rewrite the example from Listing 7.4.2, only this time we initialize the array at declaration time, and we iterate over all nested array elements using a for-each statement instead of a for-statement.
Note how we use nested curly braces to initialize the array, and how we don’t specify the dimensions of the nested arrays. Java determines the array dimensions by the initialization statement structure. Also note the type of the variable in the outer for-each loop inside printArr(…), which is for (double[] inarr : arr) {…. The loop variable is a reference to a one-dimensional double array! By now, this should make sense. In Java, a "multidimensional" array is an array of arrays; the outer array is a one-dimensional array with elements that each hold one-dimensional arrays.
// NestedArrays2.java
public class NestedArrays2 {
  public static void main(String[] args) {
    double[][] arr2D = {{0.0, 0.1, 0.2},      // 2D array
                        {1.0, 1.1, 1.2},      // Initialization
                        {2.0, 2.1, 2.2}, 
                        {3.0, 3.1, 3.2},
                        {4.0, 4.1, 4.2}};
    print2D( arr2D );                         // Print array
  }

  // Print an arbitrary 2D double array
  public static void print2D( double[][] arr ) {
    for (double[] inarr : arr) {              // Outer loop
      for (double val : inarr) {              // Inner loop
        System.out.print( val + " ");         // Print adjacent
      }
      System.out.println();                   // Next line
    }
  }
}
Listing 7.4.3. NestedArrays2.java
javac NestedArrays2.java
java NestedArrays2
0.0 0.1 0.2 
1.0 1.1 1.2
2.0 2.1 2.2
3.0 3.1 3.2
4.0 4.1 4.2

Subsection 7.4.3 Ragged Arrays

In Listing 7.4.3 we initialized our multidimensional array explicitly using nested curly braces {{…}, {…}, …}. Array dimensions were determined using the structure of the initializer. This begs the question, what would happen if we initialized the array using something like the following, an initializer without uniform inner dimensions? Would it work?
double[][] arr2D = {{0.0, 0.1, 0.2},      // Right side is "ragged"
                    {1.0, 1.1, 1.2, 1.3},
                    {2.0, 2.1}, 
                    {3.0, 3.1, 3.2},
                    {4.0}};
If it’s true that Java’s multidimensional arrays are really just arrays of arrays, then this syntax should be valid. There should be no constraint on the lengths of the inner arrays because outer array elements hold array references, which don’t care about the length of the arrays that they reference.
Listing 7.4.4 is the program in Listing 7.4.3 only with a ragged initializer. Compiling and running this program produces no errors, and the expected output. This demonstrates that it is possible to have internal arrays with unequal lengths.
// NestedArrays2.java
public class NestedArrays3 {
  public static void main(String[] args) {
    double[][] arr2D = {{0.0, 0.1, 0.2},      // Right side is "ragged"
                        {1.0, 1.1, 1.2, 1.3},
                        {2.0, 2.1}, 
                        {3.0, 3.1, 3.2},
                        {4.0}};
    print2D( arr2D );                         // Print array
  }

  // Print an arbitrary 2D double array
  public static void print2D( double[][] arr ) {
    for (double[] inarr : arr) {              // Outer loop
      for (double val : inarr) {              // Inner loop
        System.out.print( val + " ");         // Print adjacent
      }
      System.out.println();                   // Next line
    }
  }
}
Listing 7.4.4. NestedArrays3.java
javac NestedArrays3.java
java NestedArrays3
0.0 0.1 0.2 
1.0 1.1 1.2 1.3
2.0 2.1
3.0 3.1 3.2
4.0
Multidimensional arrays with unequal lengths are called ragged arrays because they appear to have ragged right edges when printed. A schematic of the arr2D array is illustrated in Figure 7.4.5.
Figure 7.4.5. A schematic of arr2D, a ragged 2D array
Multidimensional arrays in Java can even be assembled incrementally. Have a close look at our final example in Listing 7.4.6. There are several important features to note.
// RaggedAssembly.java
// Assemble a ragged array one inner array at a time.
public class RaggedAssembly {
  public static void main(String[] args) {
    double[][] arr2D = new double[5][];   // Inner lengths unspecified 
    
    // Create and assign all inner arrays using various approaches
    arr2D[0] = new double[] {0.0, 0.1, 0.2};
    arr2D[1] = new double[] {1.0, 1.1, 1.2, 1.3};
    arr2D[2] = new double[3];
    //arr2D[3] = // Leave unassigned
    arr2D[4] = new double[] {4.0};

    print2D( arr2D ); //                  // Print array
  }

  // Print an arbitrary 2D double array
  public static void print2D( double[][] arr ) {
    for (double[] inarr : arr) {          // Outer loop
      if (inarr != null) {                // Check array exists
        for (double val : inarr) {        // Inner loop
          System.out.print( val + " ");   // Print adjacent
        }
        System.out.println();             // Next line
      } else {
        System.out.println(inarr);        // Null reference
      }
    }
  }
}
Listing 7.4.6. RaggedAssembly.java
javac RaggedAssembly.java
java RaggedAssembly
0.0 0.1 0.2 
1.0 1.1 1.2 1.3
0.0 0.0 0.0
null
4.0
When we create the arr2D array in Listing 7.4.6 we leave out the size of the inner dimension (new double[5][]). This is an important step. It signals to Java that no inner arrays should be created. Immediately after the arr2D array is created, all element values will be null.
We initialize each element of arr2D one at a time, using different techniques. For the first, second, and last elements, we create and initialize one-dimensional arrays and assign them to arr2D array elements. For the third element, we create but do not initialize a one-dimensional array and assign it. As we know, the values of an uninitialized array with a numeric type will be 0. We leave the fourth array element unassigned. This leaves the value of arr2D[3] as null. All these options are permitted, because we have the flexibility of arr2D being an array of array references.
One final item to note is the update we made to the print2D(…) method. Because our internal array references can be null, we need to test for this case. After all, the outer array is just an array of references, which can have the value null. To avoid a runtime error that would occur if we tried to print arr2D with an unassigned element reference, we add an if-statement to the print2D(…) that checks that each inner array reference is not null before attempting to print its elements. If it is null, the method just goes ahead and prints the element. Looking at the output from compiling and running Listing 7.4.6, you will see that this all checks out.