4.11.1 A Grid Is an Array of Arrays
A teacher keeps quiz scores for three students, Ana, Ben, and Cy, on four quizzes each. On paper that is a small table: one line per student, one box per quiz. With the 1D arrays from Topic 4.3 you could make one array per student:
int[] anaScores = {90, 85, 77, 92};
int[] benScores = {68, 74, 81, 88};
int[] cyScores = {95, 91, 89, 100};
That works for three students, but a class of thirty would need thirty variables with thirty names, and there would be no single variable that holds “the whole class.” What the teacher wants is one variable for the whole table.
Here is the key observation. A 1D array can hold any type of element: int values, String references, and so on. An array is itself an object, so a 1D array can also hold references to other arrays. Put the three student arrays into one outer array, and that outer array holds the whole table. Java calls this a two-dimensional array, or 2D array: an array whose elements are themselves 1D arrays of the same type. That is why people describe a 2D array as an array of arrays.
You can picture it in two ways. The first is a grid. Each inner array is a row, one line across the grid. The boxes at the same position in every row line up into a column, one line going down. Rows and columns are both numbered from 0, just like 1D array indices.
| Col 0 | Col 1 | Col 2 | Col 3 | |
|---|---|---|---|---|
| Row 0 | 90 | 85 | 77 | 92 |
| Row 1 | 68 | 74 | 81 | 88 |
| Row 2 | 95 | 91 | 89 | 100 |
Row 0 is Ana’s scores, row 1 is Ben’s, and row 2 is Cy’s. Column 2 holds everyone’s score on the third quiz: 77, 81, and 89.
The second picture shows how Java actually stores it. The variable refers to one outer array with three elements, and each of those elements refers to a row array:
scores --> outer array
index 0 --> [ 90 | 85 | 77 | 92 ]
index 1 --> [ 68 | 74 | 81 | 88 ]
index 2 --> [ 95 | 91 | 89 | 100 ]
The grid picture is the one you will use most when reading code. The array-of-arrays picture explains why the rules in the rest of this chapter work the way they do, so keep both in mind.
Key idea
A 2D array is an array whose elements are 1D arrays. Each inner array is one row of a grid, and the elements at the same position in every row form a column.
Declaring and filling a 2D array
The type of a 2D array has two pairs of square brackets. int[] means “array of int,” so int[][] means “array of int[],” an array of arrays of int. The fastest way to create a small 2D array is with an initializer list, just as in Topic 4.3. The difference is that each element of the outer list is itself an initializer list for one row.
Worked example: Building the quiz table
Read this declaration one brace at a time. It is spread over three lines only so the rows line up; Java would accept it on one line.
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };| Piece | What it means |
|---|---|
int[][] |
The type: an array whose elements are int[] arrays |
scores |
The variable, which will hold a reference to the outer array |
outer { … } |
The outer array. It has three elements separated by commas, so 3 rows |
{90, 85, 77, 92} |
The first element of the outer array: row 0, a 1D array of four int |
{68, 74, 81, 88} |
Row 1 |
{95, 91, 89, 100} |
Row 2 |
Java counts the inner lists to get the number of rows (3) and counts the values inside a row to get the number of columns (4). The result is the 3-by-4 grid pictured above. People always say the number of rows first: “a 3-by-4 array” has 3 rows and 4 columns.
A 2D array can hold object references as well as primitives. Change one thing, the element type, and the same pattern builds a seating chart of names:
String[][] seats = { {"Ana", "Ben", "Cy"},
{"Dee", "Eli", "Fay"} };
The type is String[][], an array of String[] rows. There are two inner lists, so 2 rows, and each holds three names, so 3 columns. Row 1, column 0 is "Dee": count rows from 0 (row 0 is the first line, row 1 the second), then count across from 0. Each box holds a reference to a String object, exactly as the boxes of a String[] did in Topic 4.3.
Every row in these examples has the same number of elements, so the grid is a rectangle. That is always the case in this course. (Java can build a grid whose rows have different lengths, but such grids are outside AP CSA, and you will not see them on the exam.)
Watch out
Declaring a 1D type for 2D data. A student writes the quiz table with only one pair of brackets on the type:
public class WrongDimensions
{
public static void main(String[] args)
{
int[] scores = { {90, 85, 77, 92}, {68, 74, 81, 88} };
System.out.println(scores[0]);
}
}WrongDimensions.java:5: error: illegal initializer for int
int[] scores = { {90, 85, 77, 92}, {68, 74, 81, 88} };
^int[] promises that each element of the outer list is an int. The first element is {90, 85, 77, 92}, which is a whole row, not an int, so Java refuses to compile and reports the problem at each inner list. The number of bracket pairs in the type must match the nesting of the braces: two levels of braces need int[][]. The fix is to write int[][] scores = …. (The wording of compiler messages can differ a little between Java versions.)
Pause and try
Question 1 · Multiple choice
Which declaration creates a 2D array with 2 rows and 3 columns?
int[][] g = { {1, 2, 3}, {4, 5, 6} };int[][] g = { {1, 2}, {3, 4}, {5, 6} };int[] g = { {1, 2, 3}, {4, 5, 6} };int[][] g = {1, 2, 3, 4, 5, 6};
Check your reasoning
A. The number of inner lists is the number of rows, and the number of values in each inner list is the number of columns. Choice A has two inner lists of three values each: 2 rows, 3 columns.
- B is wrong: it has three inner lists of two values, so it is 3 rows by 2 columns. This choice swaps rows and columns.
- C is wrong: the type
int[]has only one pair of brackets, so each element must be anint, and Java reportsillegal initializer for int. - D is wrong: the type
int[][]says each element is anint[]row, but the list contains plain numbers, so Java reportsincompatible types: int cannot be converted to int[]. A 2D initializer list needs an inner list for every row.
Reread 4.11.1 A Grid Is an Array of Arrays if you chose differently.
Question 2 · Short answer
Using the picture of how Java stores scores (the outer array with arrows to three rows), explain in one or two sentences why a 2D array is called an “array of arrays.” How many elements does the outer array of scores have?
Check your reasoning
The outer array’s elements are not numbers. Each element is a reference to a 1D int array, one per row, so the 2D array is an array whose elements are arrays. The outer array of scores has 3 elements, one for each row. (The 12 numbers live inside the three row arrays.)
A complete answer says that each element of the outer array is itself an array (a row), and gives 3 as the number of outer elements, not 12.
Reread 4.11.1 A Grid Is an Array of Arrays if your answer differed.
4.11.2 Reading and Changing One Element
The teacher now wants Ben’s score on the third quiz. In a 1D array, one index picks one box. In a grid, one number is not enough: you have to say which row and which column. Think of a seat in a theater, which needs both a row and a seat number. “Seat 5” alone could be any of a dozen seats.
Java uses two pairs of square brackets, one index in each. The first index is the row index and the second is the column index:
scores[row][col]
The order is always row first, column second. The array-of-arrays picture shows why this works with no new rules. scores[1] follows index 1 of the outer array to Ben’s row, which is an ordinary 1D array. Then [2] picks index 2 of that row, just like any 1D array access from Topic 4.3. So scores[1][2] is really two 1D accesses in a row, read left to right.
Key idea
The expression grid[r][c] names the element in row r, column c. The first index always picks the row and the
second always picks the column, and both count from 0.
Worked example: Reading scores from the table
Each print statement uses two indices. Before reading the output, find each element in the grid in 4.11.1.
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
System.out.println(scores[1][2]);
System.out.println(scores[2][0]);
System.out.println(scores[0][3]);| Line | Expression | Row (first index) | Column (second index) | Value |
|---|---|---|---|---|
| 4 | scores[1][2] |
1, Ben’s row | 2, the third quiz | 81 |
| 5 | scores[2][0] |
2, Cy’s row | 0, the first quiz | 95 |
| 6 | scores[0][3] |
0, Ana’s row | 3, the fourth quiz | 92 |
81
95
92Notice that the column index on line 4 is 2, but it names the third quiz. Counting from 0 works in both directions, exactly as with 1D arrays.
scores[r][c] is a variable, not just a value. Anywhere you could put a plain int variable, you can put scores[r][c]: on the left of = to store a new value, with compound assignment such as += (Topic 1.6), or on the right side of an expression. This next example changes three elements.
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
scores[0][2] = 80;
scores[1][1] += 6;
scores[2][3] = scores[2][3] - scores[0][2];
System.out.println(scores[0][2]);
System.out.println(scores[1][1]);
System.out.println(scores[2][3]);80
80
20
- Line 4 stores 80 in row 0, column 2. Ana’s 77 is gone, replaced the same way any assignment replaces a value.
- Line 5 means
scores[1][1] = scores[1][1] + 6. Ben’s 74 becomes 80. - Line 6 evaluates the right side first:
scores[2][3]is 100 andscores[0][2]is now 80 (line 4 already changed it), so the result is 20, stored back in row 2, column 3.
Only the three named boxes change. Every other element keeps its value.
Watch out
Swapping the row and column. A student wants Cy’s score on quiz 1 (column 1) and writes:
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
System.out.println("Cy, quiz 1: " + scores[1][2]);Cy, quiz 1: 81Cy’s row is row 2, so the correct value is 91. The student wrote the indices in the order of the sentence, “quiz 1 for Cy,” instead of the order Java uses. scores[1][2] is row 1 (Ben), column 2, which is 81. Java gave no error, because both indices happen to be valid. That is what makes this mistake dangerous: the program runs and prints a wrong number. The fix is to put the row first: scores[2][1], which prints Cy, quiz 1: 91. When you write an access, say “row, then column” to yourself and check each index against the grid.
Pause and try
Question 3 · Tracing
Write the exact output. Draw the grid first, then update it line by line.
int[][] tiles = { {4, 7, 2},
{9, 5, 8} };
tiles[1][0] = tiles[0][1] + tiles[1][2];
tiles[0][2] *= 3;
System.out.println(tiles[1][0]);
System.out.println(tiles[0][2]);
System.out.println(tiles[1][1] - tiles[0][0]);Check your reasoning
15
6
1The starting grid has 2 rows and 3 columns:
| Col 0 | Col 1 | Col 2 | |
|---|---|---|---|
| Row 0 | 4 | 7 | 2 |
| Row 1 | 9 | 5 | 8 |
| Line | What happens | Changed element |
|---|---|---|
| 3 | tiles[0][1] is 7 and tiles[1][2] is 8, so 7 + 8 |
tiles[1][0] = 15 |
| 4 | tiles[0][2] = tiles[0][2] * 3, so 2 × 3 |
tiles[0][2] = 6 |
| 5 | Prints tiles[1][0] |
prints 15 |
| 6 | Prints tiles[0][2] |
prints 6 |
| 7 | tiles[1][1] is 5 and tiles[0][0] is 4, so 5 − 4 |
prints 1 |
Reread 4.11.2 Reading and Changing One Element if your output differed.
Question 4 · Multiple choice
Using the quiz table scores from this chapter, which expression gives Ben’s score (row 1) on the last quiz (column 3), which is 88?
scores[1][3]scores[3][1]scores[2][3]scores[1, 3]
Check your reasoning
A. The first index is the row and the second is the column, both counted from 0. Ben is row 1 and the last quiz is column 3, so scores[1][3] is 88.
- B is wrong: it puts the column first. Row 3 does not exist (the rows are 0, 1, and 2), so this access throws an
ArrayIndexOutOfBoundsExceptionwhen it runs. - C is wrong: it counts rows from 1, treating Ben as row 2. Row 2 is Cy, so this gives Cy’s 100.
- D is wrong: Java does not accept two indices inside one pair of brackets. The code does not compile; the compiler reports
']' expected. Each index needs its own pair of brackets.
Reread 4.11.2 Reading and Changing One Element if you chose differently.
4.11.3 Creating a 2D Array with new
An initializer list is handy when you already know every value. Often you do not. A theater that sells tickets needs a chart of 2 rows and 3 seats per row, with every seat starting out empty, and the program will fill seats in as people buy them. For that you need to create the grid at its full size first and put values in later.
In Topic 4.3 you created an empty 1D array with new int[5]. A 2D array uses the same keyword with two sizes:
boolean[][] taken = new boolean[2][3];
| Piece | What it means |
|---|---|
boolean[][] |
The type of the variable: an array of boolean[] rows |
new |
Create a new array object |
boolean |
The element type, the type stored in each box |
[2] |
The number of rows (always first) |
[3] |
The number of columns, the length of every row |
Java builds an outer array with 2 elements and gives it 2 row arrays of 3 elements each. Nobody has stored anything in those 6 boxes yet, but Java never leaves an element unset. When new creates a 2D array, every element starts with the default value for its type, the same defaults you met with 1D arrays:
| Element type | Default value |
|---|---|
int |
0 |
double |
0.0 |
boolean |
false |
any reference type, such as String |
null |
Java fills in defaults so that reading an element you have not yet assigned always gives a predictable answer instead of leftover garbage from memory.
Key idea
new type[rows][cols] creates a grid with that many rows and columns, and every element starts at the default value
for its type. The size is fixed when the array is created.
Worked example: Defaults in four kinds of grid
Each array below is created with new and never assigned, so every print shows a default.
int[][] counts = new int[2][3];
double[][] prices = new double[2][2];
boolean[][] taken = new boolean[3][4];
String[][] names = new String[2][5];
System.out.println(counts[1][2]);
System.out.println(prices[0][1]);
System.out.println(taken[2][3]);
System.out.println(names[0][4]);0
0.0
false
null- Line 1 makes a 2-by-3 grid of
int. Line 5 reads row 1, column 2, the last box, and gets0. - Line 2 makes a 2-by-2 grid of
double. The default prints as0.0, because it is adouble. - Line 3 makes a 3-by-4 grid of
boolean. Row 2, column 3 isfalse. - Line 4 makes a 2-by-5 grid of
String. Each box will someday hold a reference to aString, but right now it holdsnull, meaning “no object yet.” Printingnullshows the wordnull.
The sizes inside the brackets do not have to be literal numbers. Any int expression works, so a program can use variables. Here the theater stores its seat chart and sells two seats:
int rows = 2;
int seatsPerRow = 3;
boolean[][] taken = new boolean[rows][seatsPerRow];
taken[0][1] = true;
taken[1][2] = true;
System.out.println(taken[0][1]);
System.out.println(taken[0][2]);
System.out.println(taken[1][2]);true
false
true
Line 3 creates a 2-by-3 grid because rows is 2 and seatsPerRow is 3. All six seats start false, meaning not taken. Lines 4 and 5 store true in two of them. Line 7 reads a seat that was never assigned, so it still holds its default, false.
Once the grid exists, its size cannot change. There is no statement that adds a row or makes the rows longer. This comes straight from 1D arrays: the outer array and every row are ordinary arrays, and an array’s length is set when it is created. If the theater adds a row, the program must create a new, larger 2D array.
Watch out
Calling a method on an empty String box. In a grid of a reference type, every box starts as null. A student stores one name and then asks for the length of a name in a box that was never filled:
public class NullSeat
{
public static void main(String[] args)
{
String[][] seats = new String[2][3];
seats[0][0] = "Ana";
System.out.println(seats[0][0].length());
System.out.println(seats[0][1].length());
}
}3
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because ... is nullLine 7 works: seats[0][0] refers to "Ana", and its length is 3. Line 8 crashes. seats[0][1] was never assigned, so it holds null, and as you saw in Topic 1.14, calling a method on null throws a NullPointerException. Students expect an empty string, "", but new does not create any String objects at all; it only creates the boxes. (The exact message varies between Java versions.)
The fix is to check for null before calling a method, using an if statement from Topic 2.3:
public class NullSeatFixed
{
public static void main(String[] args)
{
String[][] seats = new String[2][3];
seats[0][0] = "Ana";
System.out.println(seats[0][0].length());
if (seats[0][1] != null)
{
System.out.println(seats[0][1].length());
}
else
{
System.out.println("Seat is empty");
}
}
}3
Seat is emptyPause and try
Question 5 · Multiple choice
What does this code print?
boolean[][] lights = new boolean[2][2];
lights[0][1] = true;
System.out.println(lights[1][1]);falsetruenull0
Check your reasoning
A. new fills a boolean grid with the default false. Only row 0, column 1 was assigned, so row 1, column 1 still holds false, and line 3 prints it.
- B is wrong: assigning
trueto one box changes only that box (row 0, column 1), not the whole row or column. - C is wrong:
nullis the default only for reference types such asString.booleanis a primitive. - D is wrong:
0is the default forint. Abooleancan only betrueorfalse.
Reread 4.11.3 Creating a 2D Array with new if you chose differently.
Question 6 · Tracing
Write the exact output.
double[][] fees = new double[3][2];
fees[2][1] = 12.5;
fees[0][0] = fees[2][1] * 2;
System.out.println(fees[0][0]);
System.out.println(fees[1][1]);
System.out.println(fees[2][1] + fees[2][0]);Check your reasoning
25.0
0.0
12.5Line 1 creates 3 rows and 2 columns of double, all 0.0.
| Line | What happens | Printed |
|---|---|---|
| 2 | fees[2][1] becomes 12.5 |
|
| 3 | fees[2][1] * 2 is 25.0, stored in fees[0][0] |
|
| 4 | Prints fees[0][0] |
25.0 |
| 5 | fees[1][1] was never assigned: default |
0.0 |
| 6 | 12.5 + 0.0 (row 2, column 0 is still the default) | 12.5 |
Values are double, so each prints with a decimal point.
Reread 4.11.3 Creating a 2D Array with new if your output differed.
4.11.4 One Row Is a 1D Array
Suppose a method already exists that works on one student’s scores, an int[], the kind of method you could have written in Topic 4.4. Can you hand it Ana’s row from the 2D table, or do you have to copy her four scores into a new array first?
Look at the array-of-arrays picture again. Each element of the outer array is a 1D array. So using the 2D array’s name with only one pair of brackets, scores[1], gives you the element at index 1 of the outer array: Ben’s whole row. Its type is int[], and you can use it anywhere an int[] is allowed.
| Expression | Brackets | What it names | Type |
|---|---|---|---|
scores |
none | the whole grid | int[][] |
scores[1] |
one | row 1, a 1D array | int[] |
scores[1][2] |
two | one element, row 1 column 2 | int |
Each pair of brackets peels off one level: from grid to row, then from row to a single number.
There is one more thing to remember from Topic 1.13 and Topic 4.3: arrays are objects, and a variable of an array type holds a reference. scores[1] does not make a copy of Ben’s row. It gives you a reference to the very row that lives inside the grid. If you store it in a variable, that variable and the grid share the row.
Key idea
grid[r], with one pair of brackets, is row r of the grid, a 1D array. It is the same row object that is inside the
grid, not a copy, so changing an element through it changes the grid.
Worked example: Taking out Cy's row
Line 4 stores row 2 in a 1D array variable. Watch what line 7 does to the grid.
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
int[] cyRow = scores[2];
System.out.println(cyRow[1]);
System.out.println(cyRow.length);
cyRow[0] = 99;
System.out.println(scores[2][0]);91
4
99- Line 4:
scores[2]is a reference to Cy’s row.cyRownow refers to that same row array,[95, 91, 89, 100]. - Line 5:
cyRow[1]is an ordinary 1D access, index 1 of Cy’s row:91. It is the same element asscores[2][1]. - Line 6:
cyRow.lengthis the length of the row,4, the number of columns. - Line 7 stores 99 at index 0 of the row
cyRowrefers to. - Line 8 reads
scores[2][0].scores[2]leads to the very row that line 7 changed, so it prints99, not 95.
scores --> outer array
index 0 --> [ 90 | 85 | 77 | 92 ]
index 1 --> [ 68 | 74 | 81 | 88 ]
index 2 --> [ 99 | 91 | 89 | 100 ] <-- cyRowThere is only one row 2. cyRow and scores[2] are two ways to reach it.
The real payoff is passing a row to a method. The method firstAndLast below has an int[] parameter. It has no idea that its argument came from a 2D array.
public class RowToMethod
{
public static String firstAndLast(int[] quizzes)
{
return quizzes[0] + " to " + quizzes[quizzes.length - 1];
}
public static void main(String[] args)
{
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
System.out.println("Ana: " + firstAndLast(scores[0]));
System.out.println("Ben: " + firstAndLast(scores[1]));
}
}Ana: 90 to 92
Ben: 68 to 88
On line 13, the argument scores[0] is Ana’s row, so the parameter quizzes gets a reference to that row (Topic 3.6). Inside the method, quizzes.length is 4, so quizzes[quizzes.length - 1] is quizzes[3], the last score, 92. Line 14 passes Ben’s row instead, and the same method reports 68 and 88. One method written for 1D arrays now works on any row.
Watch out
Storing a row in an int variable. A student wants Ben’s row and writes:
public class RowWrongType
{
public static void main(String[] args)
{
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
int benRow = scores[1];
System.out.println(benRow);
}
}RowWrongType.java:8: error: incompatible types: int[] cannot be converted to int
int benRow = scores[1];
^With one pair of brackets, scores[1] is a whole row, an int[], but benRow was declared to hold a single int. Students make this mistake because in a 1D array one pair of brackets always gives one element. In a 2D array, one pair gives a row and you need two pairs to reach an element. Fix it by matching the type to what you want: int[] benRow = scores[1]; for the row, or int benScore = scores[1][2]; for one score.
Pause and try
Question 7 · Multiple choice
Given double[][] temps = new double[3][5];, what is the type of the expression temps[2]?
double[]doubledouble[][]int
Check your reasoning
A. One pair of brackets on a 2D array picks one element of the outer array, and each of those elements is a row. A row of a double grid is a double[].
- B is wrong: a single
doubleneeds two indices, such astemps[2][4]. This is the 1D habit of “one bracket, one element.” - C is wrong:
double[][]is the type oftempsitself, with no brackets. Each pair of brackets removes one level. - D is wrong: the index 2 is an
int, but the expression gives what is stored at that index, which is a row ofdoublevalues.
Reread 4.11.4 One Row Is a 1D Array if you chose differently.
Question 8 · Tracing
Write the exact output.
int[][] stock = { {5, 0, 3},
{2, 8, 6} };
int[] top = stock[0];
top[1] = 4;
stock[0][2] += top[0];
System.out.println(stock[0][1]);
System.out.println(top[2]);
System.out.println(stock[1].length);Check your reasoning
4
8
3top refers to row 0 of stock; it is not a copy.
| Line | What happens | Row 0 | Row 1 |
|---|---|---|---|
| 1–2 | Grid created | [5, 0, 3] |
[2, 8, 6] |
| 3 | top refers to row 0 |
[5, 0, 3] |
[2, 8, 6] |
| 4 | top[1] = 4 changes row 0 |
[5, 4, 3] |
[2, 8, 6] |
| 5 | stock[0][2] += top[0], so 3 + 5 |
[5, 4, 8] |
[2, 8, 6] |
| 6 | stock[0][1] is 4 |
||
| 7 | top[2] is the same element as stock[0][2]: 8 |
||
| 8 | stock[1].length is the length of row 1: 3 |
If you printed 0 for line 6 or 3 for line 7, you treated top as a separate copy of the row.
Reread 4.11.4 One Row Is a 1D Array if your output differed.
4.11.5 Counting Rows and Columns
A method that receives a 2D array often has no idea how big it is. The quiz table might have 3 students or 30. To reach the last student’s last score, or just to avoid using an index that does not exist, code needs to ask the grid for its size.
A 1D array tells you its size through length (Topic 4.3). A 2D array has no separate “rows” or “columns” attribute; it does not need one, because the array-of-arrays picture gives both numbers:
- The outer array has one element per row, so
scores.lengthis the number of rows. - Each row is a 1D array whose length is the number of columns, so
scores[0].lengthis the number of columns.
Row 0 is used because it is always there, and in this course every row has the same length, so any row would give the same answer.
The same counts give the valid indices. Like any array, indices run from 0 up to one less than the length:
| Index | Valid values | For the quiz table |
|---|---|---|
| row index | 0 to scores.length - 1 |
0, 1, 2 |
| col index | 0 to scores[0].length - 1 |
0, 1, 2, 3 |
An index outside these ranges throws an ArrayIndexOutOfBoundsException, the same exception as a bad index in a 1D array, and for the same reason: one of the two 1D accesses inside grid[r][c] asked for a box that does not exist.
Key idea
For a 2D array grid, grid.length is the number of rows and grid[0].length is the number of columns. Valid row
indices are 0 through grid.length - 1, valid column indices are 0 through grid[0].length - 1, and anything else
throws an ArrayIndexOutOfBoundsException.
Worked example: Finding the last score
Lines 4 and 5 compute the last valid row and column from the lengths, instead of counting by hand.
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
int lastRow = scores.length - 1;
int lastCol = scores[0].length - 1;
System.out.println(scores.length);
System.out.println(scores[0].length);
System.out.println(scores[lastRow][lastCol]);3
4
100| Line | Expression | Why | Value |
|---|---|---|---|
| 4 | scores.length - 1 |
3 rows, so the last row index is 3 − 1 | 2 |
| 5 | scores[0].length - 1 |
row 0 has 4 elements, so last column is 3 | 3 |
| 6 | scores.length |
elements in the outer array, one per row | 3 |
| 7 | scores[0].length |
elements in row 0 | 4 |
| 8 | scores[lastRow][lastCol] |
scores[2][3], Cy’s last quiz |
100 |
If the teacher adds a fourth student row to the initializer list, lines 4 and 5 adjust by themselves, and line 8 still finds the bottom-right score.
Now change the shape. This grid, made with new, has more rows than columns.
int[][] board = new int[5][2];
board[board.length - 1][board[0].length - 1] = 7;
System.out.println(board.length);
System.out.println(board[0].length);
System.out.println(board[4][1]);5
2
7
new int[5][2] makes 5 rows of 2 columns, so board.length is 5 and board[0].length is 2. Line 2 stores 7 at board[4][1], the bottom-right box: row index 4 (5 − 1) and column index 1 (2 − 1). When a grid is not square, mixing up the two lengths gives a different answer, which the next Watch Out shows.
Watch out
Using the wrong length. A student wants Ana’s last quiz and uses scores.length for the column:
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
System.out.println("Ana's last quiz: " + scores[0][scores.length - 1]);Ana's last quiz: 77The answer should be 92. scores.length is the number of rows, 3, so scores.length - 1 is 2, and the program read column 2. The quiz table has 3 rows but 4 columns, so the two lengths are not interchangeable. On a square grid the bug would hide, which is why it slips through. Use scores[0].length - 1 for the last column.
The other common slip forgets the - 1:
public class PastLastColumn
{
public static void main(String[] args)
{
int[][] scores = { {90, 85, 77, 92},
{68, 74, 81, 88},
{95, 91, 89, 100} };
System.out.println("Ben's last quiz:");
System.out.println(scores[1][scores[0].length]);
}
}Ben's last quiz:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 4Line 9 asks for column scores[0].length, which is 4, but the columns are 0 through 3. Java throws an ArrayIndexOutOfBoundsException when the program runs. The message names the bad index (4) and the length of the array it was used on (Ben’s row, length 4); the wording can vary between Java versions. Writing scores[1][scores[0].length - 1] prints 88.
Pause and try
Question 9 · Multiple choice
Given String[][] menu = new String[4][6];, which describes all the valid indices for menu[r][c]?
rfrom 0 to 3 andcfrom 0 to 5rfrom 1 to 4 andcfrom 1 to 6rfrom 0 to 4 andcfrom 0 to 6rfrom 0 to 5 andcfrom 0 to 3
Check your reasoning
A. menu.length is 4 rows and menu[0].length is 6 columns. Indices start at 0 and end at length − 1, so rows are 0–3 and columns are 0–5.
- B is wrong: it counts from 1. Index 0 is valid, and index 4 for a row would throw an
ArrayIndexOutOfBoundsException. - C is wrong: it forgets the
- 1. Index 4 for the row (or 6 for the column) is one past the end. - D is wrong: it swaps rows and columns. The first size in
new String[4][6]is the number of rows.
Reread 4.11.5 Counting Rows and Columns if you chose differently.
Question 10 · Tracing
Write the exact output.
int[][] marks = new int[2][5];
marks[marks.length - 1][0] = marks[0].length;
marks[0][marks[0].length - 1] = marks.length * 10;
System.out.println(marks[1][0]);
System.out.println(marks[0][4]);
System.out.println(marks[1][4]);Check your reasoning
5
20
0marks has 2 rows (marks.length is 2) and 5 columns (marks[0].length is 5), all starting at 0.
| Line | Index expressions | Becomes | Assigned value |
|---|---|---|---|
| 2 | marks[2 - 1][0] |
marks[1][0] |
marks[0].length, 5 |
| 3 | marks[0][5 - 1] |
marks[0][4] |
marks.length * 10, 20 |
| 4–6 | Prints marks[1][0], marks[0][4], marks[1][4] |
5, 20, and the default 0 |
Reread 4.11.5 Counting Rows and Columns if your output differed.
Putting it together
This program runs a small ticket booth. It creates a seat chart with new, fills seats using indices, uses length to find the last row and column, changes a seat through a row variable, and checks for an empty seat before treating it as a name.
public class TicketBooth
{
public static void main(String[] args)
{
String[][] seats = new String[3][4];
seats[0][0] = "Ana";
seats[1][3] = "Ben";
seats[seats.length - 1][0] = "Cy";
String[] middle = seats[1];
middle[1] = "Dee";
System.out.println("Rows: " + seats.length);
System.out.println("Seats per row: " + seats[0].length);
System.out.println("Row 1, seat 1: " + seats[1][1]);
System.out.println("Row 2, seat 0: " + seats[2][0]);
System.out.println("Last seat: " + seats[seats.length - 1][seats[0].length - 1]);
if (seats[0][2] == null)
{
System.out.println("Row 0, seat 2 is open");
}
}
}The chart has 3 rows and 4 seats per row, and every box starts as null.
| Line(s) | What happens | Seats filled so far |
|---|---|---|
| 5 | new String[3][4] makes 12 boxes, all null |
none |
| 6 | Row 0, seat 0 gets "Ana" |
[0][0] Ana |
| 7 | Row 1, seat 3 gets "Ben" |
+ [1][3] Ben |
| 8 | seats.length - 1 is 2, the last row; row 2, seat 0 gets "Cy" |
+ [2][0] Cy |
| 9 | middle refers to row 1 itself, not a copy |
(no change) |
| 10 | middle[1] is the same box as seats[1][1]; it gets "Dee" |
+ [1][1] Dee |
| 11–12 | seats.length is 3 rows; seats[0].length is 4 seats per row |
|
| 13 | seats[1][1] was set through middle |
|
| 14 | seats[2][0] was set on line 8 |
|
| 15 | seats[2][3], the bottom-right seat, was never assigned, so it is null |
|
| 16–19 | seats[0][2] is still null, so the condition is true |
Rows: 3
Seats per row: 4
Row 1, seat 1: Dee
Row 2, seat 0: Cy
Last seat: null
Row 0, seat 2 is open
Line 15 prints the word null by joining it to a string, which is safe; only calling a method on null would crash. Line 16 avoids that kind of crash by comparing with null before using the seat.
Common mistakes
Each of these mistakes was taught in its own subsection. This table lets you look up a message you see when your own code goes wrong.
| What you see | Likely cause | Fix |
|---|---|---|
illegal initializer for int (compile error) |
A 2D initializer list stored in a 1D type such as int[] |
Declare the variable as int[][] |
incompatible types: int[] cannot be converted to int |
One pair of brackets (a whole row) stored in an int variable |
Use int[] for a row, or add the column index for one element |
ArrayIndexOutOfBoundsException (runtime) |
An index equal to a length, a missing - 1, or row and column swapped |
Keep rows in 0 to grid.length - 1, columns in 0 to grid[0].length - 1 |
NullPointerException (runtime) |
A method called on an unfilled box of a reference-type grid | Assign the box first, or check != null |
| Wrong value, no error | Row and column swapped, or grid.length used for columns |
Row first, then column; columns come from grid[0].length |
Vocabulary
| Term | Definition | Example |
|---|---|---|
| two-dimensional (2D) array | An array whose elements are 1D arrays of the same type, pictured as a grid | int[][] scores |
| array of arrays | How Java stores a 2D array: an outer array whose elements refer to row arrays | scores refers to 3 row arrays |
| row | One inner array of a 2D array, a line across the grid | scores[1] is {68, 74, 81, 88} |
| column | The elements at the same index in every row, a line down the grid | column 2 of scores is 77, 81, 89 |
| row index | The first index in grid[r][c], which picks the row |
the 1 in scores[1][2] |
| column index | The second index in grid[r][c], which picks the column |
the 2 in scores[1][2] |
| default value (review) | The value new puts in every element: 0, 0.0, false, or null depending on the type |
new int[2][3] holds six 0s |
Chapter summary
- A 2D array is an array of arrays: an outer array whose elements are 1D row arrays of the same type. Picture it as a grid of rows and columns, both numbered from 0. It can hold primitives or object references, and in this course every row has the same length.
- Create a 2D array with an initializer list of row lists, such as
{ {1, 2, 3}, {4, 5, 6} }, or withnew type[rows][cols], which fills every element with its default value:0,0.0,false, ornull. The size cannot change after creation. grid[r][c]reads or changes one element. The first index is always the row and the second is always the column.grid[r], with one pair of brackets, is rowras a 1D array. It is the same row object as the one inside the grid, so changes made through it show up in the grid.grid.lengthis the number of rows andgrid[0].lengthis the number of columns. Indices outside 0 to length − 1 throw anArrayIndexOutOfBoundsException.
Review questions
Question 11 · Multiple choice
A weather program creates double[][] rainfall = new double[4][7]; to store 4 weeks (rows) of 7 days (columns). Which statement is true right after this line runs?
rainfallhas 7 rows and 4 columns.rainfall[3][6]holds0.0.rainfall[4][7]is the last element.rainfall.lengthis28.
Check your reasoning
B. The first size is the number of rows, so the grid has 4 rows and 7 columns. The last valid indices are 3 and 6, and new filled every double element with 0.0. So rainfall[3][6] exists and holds 0.0.
- A is wrong: it reverses the sizes. In
new double[4][7], the 4 is rows. - C is wrong: indices end at length − 1. Row 4 and column 7 are both one past the end, so
rainfall[4][7]throws anArrayIndexOutOfBoundsException. - D is wrong:
rainfall.lengthcounts the elements of the outer array, which is one per row: 4. The 28 elements are spread across the rows.
Reread 4.11.3 Creating a 2D Array with new and 4.11.5 Counting Rows and Columns if you chose differently.
Question 12 · Tracing
Write the exact output.
int[][] grid = { {3, 1, 4, 1},
{5, 9, 2, 7} };
int[] second = grid[1];
grid[0][grid.length] = second[3];
second[0] -= grid[0][0];
System.out.println(grid[0][2]);
System.out.println(grid[1][0]);
System.out.println(grid.length + grid[1].length);Check your reasoning
7
2
6grid has 2 rows and 4 columns. second refers to row 1 itself.
| Line | What happens | Row 0 | Row 1 |
|---|---|---|---|
| 1–2 | Grid created | [3, 1, 4, 1] |
[5, 9, 2, 7] |
| 3 | second refers to row 1 |
[3, 1, 4, 1] |
[5, 9, 2, 7] |
| 4 | grid.length is 2 (rows), so this is grid[0][2] = second[3], which is 7 |
[3, 1, 7, 1] |
[5, 9, 2, 7] |
| 5 | second[0] = 5 - grid[0][0], so 5 − 3 = 2, stored in row 1 itself |
[3, 1, 7, 1] |
[2, 9, 2, 7] |
| 6 | grid[0][2] |
prints 7 |
|
| 7 | grid[1][0] is the box line 5 changed through second |
prints 2 |
|
| 8 | grid.length + grid[1].length is 2 + 4 |
prints 6 |
Line 4 uses a row count as a column index. That is legal here because 2 is a valid column, but it is the kind of length mix-up to watch for.
Reread 4.11.4 One Row Is a 1D Array and 4.11.5 Counting Rows and Columns if your output differed.
Question 13 · Debugging
This program should print the bottom-right temperature, 69.0. Instead it prints Last reading: and then crashes with an ArrayIndexOutOfBoundsException. Explain the error and correct line 8.
public class BottomRight
{
public static void main(String[] args)
{
double[][] temps = { {61.5, 70.0, 66.5},
{58.0, 73.5, 69.0} };
System.out.println("Last reading:");
System.out.println(temps[temps.length][temps[0].length]);
}
}Check your reasoning
temps.length is 2 and temps[0].length is 3, so line 8 asks for temps[2][3]. The valid rows are 0 and 1, and the valid columns are 0, 1, and 2. Row index 2 is one past the last row, so Java throws the exception as soon as it tries to find row 2, before it even looks at the column. A length counts the elements; the last index is always one less.
Corrected line 8:
System.out.println(temps[temps.length - 1][temps[0].length - 1]);This reads temps[1][2], and the program prints Last reading: and then 69.0.
Reread 4.11.5 Counting Rows and Columns if your fix differed.
Question 14 · Short answer
A student asks: “Why is the number of columns grid[0].length and not something like grid.columns? And why row 0? Would grid[1].length work too?” Answer the student in two or three sentences, using the idea of an array of arrays.
Check your reasoning
A 2D array is an outer array whose elements are row arrays. grid.length counts the outer array’s elements, one per row, and grid[0] is the first row, a 1D array, so its length is the number of elements in a row, which is the number of columns. Row 0 is used because it is always there, but every row in this course has the same length, so grid[1].length (when the grid has at least 2 rows) gives the same answer.
A complete answer mentions that grid[0] is a row (a 1D array), that a row’s length is the column count, and that all rows have equal length here, so any existing row works.
Reread 4.11.1 A Grid Is an Array of Arrays and 4.11.5 Counting Rows and Columns if your answer differed.
Question 15 · Multiple choice
What does this code print?
int[][] nums = { {2, 4},
{6, 8},
{10, 12} };
System.out.println(nums[2][1] + nums.length * nums[0].length);181224- Nothing; it throws an
ArrayIndexOutOfBoundsExceptionbecausenumshas only 2 columns
Check your reasoning
A. The initializer list has 3 inner lists of 2 values, so nums.length is 3 and nums[0].length is 2. nums[2][1] is row 2, column 1: 12. Multiplication happens before addition, so the expression is 12 + 3 × 2 = 18.
- B is wrong: it counts from 1, reading
nums[2][1]as “second row, first column,” which is 6, and then 6 + 6 = 12. - C is wrong: it treats
nums.lengthas 6, the total number of values, giving 12 + 6 × 2 = 24.lengthon the outer array counts rows, so it is 3. - D is wrong: it reads
[2]as the column. The first index is the row, and row 2 exists (rows 0 to 2). The column index 1 is valid too.
Reread 4.11.2 Reading and Changing One Element and 4.11.5 Counting Rows and Columns if you chose differently.
Question 16 · Debugging
A school tracks absences for 3 school days (rows) and 4 class periods (columns), as the comment says. The program crashes with an ArrayIndexOutOfBoundsException before printing anything. Find the error and correct the line that causes it.
public class AttendanceChart
{
public static void main(String[] args)
{
// 3 school days (rows) by 4 class periods (columns)
int[][] absences = new int[4][3];
absences[0][1] = 2;
absences[2][3] = 1;
System.out.println(absences[0][1] + absences[2][3]);
}
}Check your reasoning
Line 6 creates the array with the sizes reversed. new int[4][3] makes 4 rows of 3 columns, so the valid column indices are only 0, 1, and 2. Line 7 is fine, but line 8 uses column 3, which does not exist, and Java throws the exception there. The student wrote the sizes as “4 periods, 3 days,” but the first size is always the number of rows.
Corrected line 6:
int[][] absences = new int[3][4];Now there are 3 rows (days) and 4 columns (periods), absences[2][3] is the last box of the last row, and the program prints 3.
Changing line 8 to a valid index would also stop the crash, but it would record the absence in the wrong place; the real error is the shape of the array.
Reread 4.11.3 Creating a 2D Array with new if your fix differed.
Reflection
- I can explain why a 2D array is called an array of arrays and draw one as a grid of rows and columns. Reread
- I can read a 2D initializer list and say how many rows and columns it creates. Reread
- I can read and change one element with two indices, row first and column second. Reread
- I can create a 2D array with new and say what default value every element starts with. Reread
- I can use one pair of brackets to get a row and explain why changing that row changes the grid. Reread
- I can find the number of rows and columns with length and name every valid index. Reread