Перейти к содержимому
Теория · Массивы · урок 7 из 8

Двумерные массивы

Ряд шкафчиков хорош для списка: баллы игроков, цены, оценки. Но многие данные устроены как таблица. Место в кинозале — это ряд и номер кресла, клетка на игровом поле — строка и столбец.

В спортзале шкафчики тоже часто стоят стеной: несколько рядов один над другим. Чтобы найти шкафчик, нужны два номера — ряд и место в ряду. В Java такую стену собирают из обычных массивов: у каждого ряда свой массив, а ещё один массив хранит талоны ко всем рядам.

JAVA
public class Main {
    public static void main(String[] args) {
        int[][] grid = new int[2][3];
        grid[1][2] = 7;
        System.out.println(grid[1][2]);     // 7
        System.out.println(grid[0][0]);     // 0
        System.out.println(grid.length);    // 2
        System.out.println(grid[0].length); // 3
    }
}

Давайте разберём программу шаг за шагом:

  1. new int[2][3] создаёт таблицу из двух строк по три элемента, и во всех элементах лежит 0.
  2. grid[1][2] = 7; кладёт 7 во вторую строку, в третий столбец.
  3. grid[1][2] печатает 7.
  4. grid[0][0] — первый элемент первой строки, в него ничего не клали: 0.
  5. grid.length печатает 2, а grid[0].length — 3.

Массив массивов

Двумерный массив — массив, элементы которого тоже массивы. Тип int[][] читается как «массив массивов int». Запись new int[2][3] создаёт внешний массив из двух элементов, и каждый элемент — ссылка на свою строку, массив int[3].

Индексы: строка и столбец

Запись grid[1][2] читается слева направо:

  1. grid[1] берёт из внешнего массива вторую строку. Это обычный int[].
  2. [2] берёт из этой строки третий элемент.

Поэтому первый индекс выбирает строку, а второй — столбец, то есть место в строке. Оба считаются с нуля, как в любом массиве.

Две длины

У таблицы два размера, и каждый берут у своего массива:

  • grid.length — длина внешнего массива, то есть число строк: 2;
  • grid[0].length — длина первой строки, то есть число столбцов: 3;
  • всего элементов 2 * 3, то есть шесть, но grid.length их не считает.

На собеседовании любят спросить, что вернёт grid.length для new int[2][3]. Ответ — 2, число строк, а не число всех элементов.

Обход вложенными циклами

Таблицу можно задать и значениями сразу: каждая пара внутренних фигурных скобок — одна строка. Чтобы обойти все элементы, нужны два цикла, один внутри другого:

JAVA
int[][] table = {{1, 2, 3}, {4, 5, 6}};
int sum = 0;
for (int i = 0; i < table.length; i++) {
    for (int j = 0; j < table[i].length; j++) {
        sum = sum + table[i][j];
    }
}
System.out.println(sum); // 21

Разберём, как работают циклы:

  1. Внешний цикл идёт по строкам: i пробегает 0 и 1, пока i < table.length.
  2. Внутренний цикл идёт по столбцам текущей строки: j пробегает 0, 1 и 2, пока j < table[i].length.
  3. На каждой строке внутренний цикл проходит целиком, поэтому тело выполняется шесть раз.
  4. sum собирает все шесть элементов и печатает 21.

Здесь картинка со стеной шкафчиков неточна. В настоящей стене все ряды одной длины, а в Java каждая строка — отдельный массив, и строки могут быть разной длины: {{1, 2}, {3, 4, 5}}. Поэтому во внутреннем цикле надёжнее писать table[i].length, длину именно текущей строки.

Печать таблицы

Arrays.toString печатает один массив, поэтому для таблицы его хватает только на одну строку. Всю таблицу печатает Arrays.deepToString:

JAVA
System.out.println(Arrays.toString(table[1]));  // [4, 5, 6]
System.out.println(Arrays.deepToString(table)); // [[1, 2, 3], [4, 5, 6]]

Если отдать всю таблицу в Arrays.toString, напечатаются не числа, а служебные записи вида [I@ для каждой строки. Элементы внешнего массива — ссылки, и toString печатает их. Метод deepToString проходит по каждой ссылке и печатает саму строку.

Частая ошибка

Перепутали две длины в заголовках циклов:

JAVA
int[][] table = {{1, 2, 3}, {4, 5, 6}};
int sum = 0;
for (int i = 0; i < table[0].length; i++) { // ✗
    for (int j = 0; j < table.length; j++) {
        sum = sum + table[i][j];
    }
}
TEXT
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2

Внешний цикл выбирает строку, но его граница — table[0].length, число столбцов, то есть 3. На третьем круге i становится 2, а строк всего две, с индексами 0 и 1. Строки ограничивает table.length, а столбцы — table[i].length.

Главное

  • Двумерный массив — массив массивов: new int[2][3] создаёт две строки по три элемента.
  • В grid[i][j] первый индекс выбирает строку, а второй — столбец.
  • grid.length — число строк, а grid[0].length — длина первой строки, то есть число столбцов.
  • Таблицу обходят вложенными циклами: внешний до grid.length, внутренний до grid[i].length.