Concepts / Indexing Into Two-Dimensional Arrays

Indexing Into Two-Dimensional Arrays

Java does not have true two-dimensional arrays. A two-dimensional array is an array whose elements are one-dimensional arrays.

  • Programming

The Grid Picture You Bring to Java

Many programmers arrive at Java already picturing a two-dimensional array as a neat grid: rows running across, columns running down, and every cell sitting inside one solid rectangle of storage. That picture is useful, and in many situations it will serve you well. It rests on an assumption, however, that does not hold in Java. This article examines that assumption directly and replaces it with a more accurate model of what Java does when you write a grid-shaped array.

What do you think happens?

Given int[][] A = new int[3][4];, what do you think A[1] holds: a single int, a row of four ints, or the whole grid?

  • A single int

  • A row of four ints, which is itself an array

  • The whole grid

Reveal answer
Answer:

A row of four ints, which is itself an array (an int[] of length 4).

Java treats A as an array whose elements are one-dimensional int arrays. So A[1] is one of those elements, and it is a complete int[] with four slots. Keep this answer in mind, because the rest of the article depends on it.

Why Java Has No True Two-Dimensional Arrays

Java does not have true two-dimensional arrays. What Java provides instead is a two-dimensional array in the sense that a two-dimensional array is an array whose elements are one-dimensional arrays. Java has no single rectangular block of storage that is declared as a grid. It has an outer array, and each of its elements is a separate one-dimensional array.

The grid picture is still a useful mental model for everyday work. Reading and writing a cell such as A[1][2] behaves the way a grid suggests, and most code never needs more than that. The grid picture stops being accurate, though, when you ask where the rows physically live and how they are connected. The accurate answer is that the outer array holds references to rows, and each row is its own array.

Reading the Type int[][]

The type names themselves reveal the real structure. Java follows a simple rule: for any base type, you can form a new type by adding a pair of square brackets, and the result means array of that base type. Start with int[], which means array of int. Now treat int[] as the base type and add brackets again. You get int[][], which means array of int[], or equivalently array of arrays of int. The type name is a construction recipe, and it tells you exactly what the outer array holds.

Definition

The type int[][] means an array whose elements are of type int[]. Each element of the outer array is a variable of type int[] that refers to one row.

Tracing What the Outer Array Holds

Consider a 3-by-4 array named A. Because A is an array of int[], you can name its three rows as A[0], A[1], and A[2]. Each of these is a complete one-dimensional array of int, and each has four slots. The diagram below shows the structure: the outer array holds three references, and each reference leads to its own row.

Aouter array of int[]A[0]int[] with 4 slotsA[1]int[] with 4 slotsA[2]int[] with 4 slotsrefers torefers torefers to
When I declare int[][] A, what is stored in A itself, and what does each element of A hold?

Notice what the diagram does not show. It does not show the three rows sitting end to end in one unbroken stretch, and it does not show a single rectangular region. Each row is an independent one-dimensional array that the outer array points to. A useful way to hold this in mind is that the outer array is the table of contents and the rows are the pages.

Following grid[r][c] One Index at a Time

Now trace a single access. Each index in A[1][2] is applied in order, from left to right. The first index selects an element of the outer array, and that element is a row. The second index then selects one slot inside that row.

Aouter array of int[]A[1]row, an int[]A[1][2]single int cellindex 1 selects rowindex 2 selects cell
How does A[1][2] move from the outer array to a row reference and then to a single element?
Worked example

Reading and writing a cell through two indexes

A is a 3-by-4 int array. Set the cell in row 1, column 2 to 5, then read it back.

  1. 1
    Select the row

    A[1] is the element at index 1 of the outer array. That element is an int[] reference to the second row.

  2. 2
    Select the cell

    A[1][2] applies index 2 to that row, which selects the third slot of the second row.

  3. 3
    Assign and read

    Assigning 5 changes the slot inside row 1. Reading A[1][2] afterward returns that 5.

Result

A[1][2] holds 5, and the row it lives in is the row that A[1] refers to.

java
Output
3
4
5
Notes on the code
  • A.length counts the rows held by the outer array, which is 3.
  • A[0].length goes through the first row reference and counts its slots, which is 4.
  • A[1][2] = 5 uses both indexes: the first picks the row, the second picks the cell.

Passing a Row to a Method

The row-by-row structure matters in one particularly important situation. Because each row is a value of type int[], a single row can be handed to any subroutine that asks for an int[] parameter. The expression A[1] is an int[] value, so it can be passed directly as an argument.

Code walkthrough
static void fill(int[] row) {
    row[0] = 7;
}

public static void main(String[] args) {
    int[][] A = new int[3][4];
    fill(A[1]);
    System.out.println(A[1][0]);
}
  1. 1
    The method fill takes one parameter of type int[], which is exactly the type of a row.
  2. 2
    In main, A is created as a 3-by-4 array, so A[1] is a row of four ints.
  3. 3
    The call fill(A[1]) passes that row. The parameter row now refers to the same array object that A[1] refers to.
  4. 4
    Inside fill, row[0] = 7 writes into that shared row, not into a copy of it.
  5. 5
    Back in main, A[1][0] reads the same slot and gets 7.
Output
7
mainholds Afillparameter row, an int[]A[1][0]7passes A[1]writes row[0]
When I pass grid[1] to a method and the method changes an element, does the caller's row change too, and why?

The diagram shows why the caller's row changes. The method does not receive a copy of the row. It receives the same row reference, so writes made through the parameter are visible through A[1] once the method returns. This is the practical consequence of the array-of-arrays structure: a row is a real array object that can be shared, not a detached piece of a rectangle.

Mistakes Beginners Make With Rows

  • Common mistake

    Treating A[1] as a single number

    What it looks like
    int x = A[1]; where A is int[][]
    Why it fails

    A[1] is an element of the outer array, and that element has type int[]. It is a whole row, not an int.

    Do this instead

    Use int[] row = A[1]; to hold the row, or A[1][2] to reach a single int.

  • Common mistake

    Assuming the rows form one solid block of storage

    Why it fails

    Each row is a separate one-dimensional array that the outer array refers to. The grid picture describes how you index, not how the rows are arranged.

    Do this instead

    Think of the outer array as holding references to rows, and reason about rows as independent array values.

  • Common mistake

    Expecting a passed row to be copied

    What it looks like

    fill(A[1]) followed by reading A[1][0] after fill changes row[0]

    Why it fails

    The method receives the same row reference, so changes made through the parameter appear in the caller's row.

    Do this instead

    If you need the caller's row unchanged, make your own copy before modifying it.

Practice Predicting Row Effects

MEDIUM

Given int[][] A = new int[3][4]; and a method static void zero(int[] row) { row[3] = 0; }, what does A[2][3] hold after the call zero(A[2]) runs when the array was initially filled with 9 in every cell? Explain your answer in terms of which row the parameter refers to.

Hints
  • Ask what type A[2] has, and whether the method receives a copy or a reference.
  • Find which slot row[3] refers to, and whether that slot belongs to A[2].
MEDIUM

Write a method static void clearRow(int[] row) that sets every slot of the row to 0. Then call it on the second row of a 3-by-4 array and print the whole array to confirm that only that row changed.

Hints
  • A loop over row.length visits every slot in the row.
  • Only the row you pass in should change; the other rows should keep their values.

Putting the Two Models Together

  1. 1
    Java does not have true two-dimensional arrays. A two-dimensional array is an array whose elements are one-dimensional arrays.
  2. 2
    The type int[][] means array of int[], so each element of the outer array is a variable of type int[] that refers to one row.
  3. 3
    Indexing as A[r][c] works the way a grid suggests: the first index selects a row, and the second selects a cell within it.
  4. 4
    Because each row is a value of type int[], a single row can be passed to any method that accepts an int[] parameter, and the method shares that row with the caller.

Key Takeaways

  • Java does not have true two-dimensional arrays; a two-dimensional array is an array whose elements are one-dimensional arrays.
  • The type int[][] means array of int[], so the outer array holds references to rows.
  • In A[r][c], the first index selects a row and the second selects a cell inside that row.
  • Because each row is an int[] value, passing a row to a method shares that row with the caller.