The Concept of a 2D Array
We know that a 1D array can be thought of as a single column of values. A two-dimensional array (or “2D Array”) can be visualized as a table of values with both rows and columns. For example, below is a 2D array with 3 rows and 4 columns.
To refer to a particular element in a 2D array, we must specify two indices, the first for the row and the second for the column. For example, to access the element in the bottom row and second column of the array illustrated, we would reference numbers[2][1] in our code. Notice that indices always start from 0.
Creating and Initializing 2D Arrays
In Java you can create and initialize a 2D array in much the same way as a 1D array:
1 2 |
// Method 1: declare & initialize to default values (0, 0.0, false, null) in 1 step int[][] numbers = new int[3][4]; |
The initial value for all elements in the array above will be 0, as you would expect. Using a two-step process, we can also declare a 2D array reference variable, and later in our code actually instantiate the array.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Method 2: declare reference variable for 2D array of integers int[][] numbers2; Scanner input = new Scanner( System.in ); int rows; int cols; System.out.print( "How many rows for array? " ); rows = input.nextInt(); System.out.print( "How many columns for array? " ); cols = input.nextInt(); // Later in code, instantiate the actual 2D integer array numbers2 = new int[rows][cols]; |
Finally, in one step it’s possible to instantiate a 2D array object and initialize each element to a starting value other than the default:
1 2 |
// Method 3: declare and initialize to custom values in 1 step double[][] marks = { { 56.7, 72.8, 93.5 }, { 84.2, 50.1, 86.8 } }; |
This statement instantiates a 2D array object with 2 rows and 3 columns (i.e., 6 elements), and initializes its elements to starting values other than 0.0. The new keyword is not required as the Java compiler can figure out the dimensions of the array automatically.
To access a particular element in a 2D array we must include both indices. For example:
1 2 3 4 5 6 7 8 |
// Display the bottom-right mark System.out.println( "marks[1][2] = " + marks[1][2] ); // Add 10 to the bottom-right mark marks[1][2] += 10; // Display the new bottom-right mark System.out.println( "NEW marks[1][2] = " + marks[1][2] ); |
marks[1][2] = 86.8
NEW marks[1][2] = 96.8
“Ragged Arrays”
The 2D array pictured at the top of this page is a convenient way to think about 2D arrays, but in Java they are not actually stored this way. In Java, a 2D array is really a 1D array whose elements refer to other 1D arrays. What this means is that we can define a 2D array where each row has a different number of columns.
When every row of a 2D array has the same number of columns, it’s called a rectangular array. When the rows of a 2D array have a different number of columns it’s called a ragged array. Ragged arrays can be more efficient in terms of memory since you can avoid having empty/unused elements in your array.
Here is one method to create a ragged array:
1 2 3 4 5 |
int numbers[][]; // declare a 2D array reference variable numbers = new int[3][]; // create 3 rows numbers[0] = new int[4]; // create 4 columns for row 0 numbers[1] = new int[2]; // create 2 columns for row 1 numbers[2] = new int[3]; // create 3 columns for row 2 |
Alternatively, you can instantiate and initialize (to values other than 0) in one step:
1 |
int[][] numbers2 = { { 6, 9, 3, 7 }, { 5, 2 }, { 8, 4, 1 } }; |
Here is what the numbers array would look like in memory:
As illustrated, a two-dimensional array in Java is really an array of arrays. It’s possible to extend the syntax we have just learned to arrays with 3 (or more) dimensions (i.e., 3 or more indices), but at this stage two-dimensional arrays are enough.
The length Attribute
Since a 2D array in Java is actually a 1D array where each element refers to another 1D array, we need to be careful when using the length attribute.
1 2 3 4 5 6 7 8 |
// Create a ragged array of marks double[][] marks = { { 56.7, 72.8, 93.5 }, { 84.2, 50.1, 86.8, 67.8 }, { 45.6, 98.2 } }; // Using .length with 2D arrays System.out.printf( "\nmarks array has %d rows", marks.length ); System.out.printf( "\n first row has %d cols", marks[0].length ); System.out.printf( "\nsecond row has %d cols", marks[1].length ); System.out.printf( "\n third row has %d cols\n\n", marks[2].length ); |
marks array has 3 rows
first row has 3 cols
second row has 4 cols
third row has 2 cols
In the example above, marks.length refers to the total number of rows in the array. Since each row in a 2D array can have a different number of columns we need to be careful not to assume that each row is the same length. As shown, the number of columns in the first row (i.e., marks[0].length) is 3, and the number of columns in the second row (i.e., marks[1].length) is 4.
We also need to be careful when attempting to display the contents of a 2D array. This fails, as you would expect:
1 |
System.out.println( marks ); |
[[D@38ad5fab
But so does this, because Arrays.toString() only works with 1D Arrays:
1 |
System.out.println( Arrays.toString( marks ) ); |
[[D@354d581b, [D@68d36ff3, [D@115af049]
What we are actually seeing above are the 3 memory locations of the 1D arrays (one for each row) that make up the 2D marks array. To actually print each row using Arrays.toString(), we need to include a row index:
1 2 3 |
System.out.println( Arrays.toString( marks[0] ) ); System.out.println( Arrays.toString( marks[1] ) ); System.out.println( Arrays.toString( marks[2] ) ); |
[56.7, 72.8, 93.5]
[84.2, 50.1, 86.8, 67.8]
[45.6, 98.2]
2D Array Processing
Since 2D arrays have 2 indices, nested for loops are the best way to process them. Here is an example that not only displays the contents of a ragged 2D array but also determines the average of all of the elements.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// How to display/process a 2D array using nested for loops double total = 0; int markCount = 0; System.out.println("Array contents:"); System.out.println("----- col0 col1 col2 col3"); for (int row = 0; row < marks.length; row++) { System.out.printf("row%d", row); for (int col = 0; col < marks[row].length; col++) { System.out.printf(" %.1f", marks[row][col]); total += marks[row][col]; markCount++; } System.out.println(); } System.out.printf( "Average of all marks is %.1f%%.\n", total / markCount ); |
Array contents:
—— col0 col1 col2 col3
row0 56.7 72.8 93.5
row1 84.2 50.1 86.8 67.8
row2 45.6 98.2
Average of all marks is 72.9%.
On line 6, the outer loop uses marks.length to ensure that each row of the 2D array is processed. The inner loop, on line 8, uses marks[row].length to process the columns in each row, independently. The use of the length attribute in this way ensures that these nested loops can handle a rectangular or ragged marks array of any size without having to make code changes.
You try!
- Write a program that prompts a user for the dimensions of a rectangular 2D array of integers. Instantiate the 2D array, then use nested loops to initialize each element with a random integer between 1 and 99. Finally, using nested loops again, neatly display the contents of the array, and report the largest and smallest integers found within it.