Что такое одномерный массив?

Массив — это упорядоченное множество элементов одного типа, примитивного или ссылочного. Общую информацию о массивах (преимущественно одномерных) можно найти в статье “Массивы в Java” и в курсе JavaRush. В этой статье речь пойдёт о массивах, элементами которых являются другие массивы. Такие массивы называются многомерными. Массив, элементами которого являются другие массивы, то есть массив массивов, называется двумерным. Не во всех языках многомерные массивы имеют такую структуру, но в Java дела обстоят именно так.

Многомерные массивы, общий синтаксис

В обобщённом виде многомерные массивы в Java выглядят так:
Data_type[dimension1][dimension2][]..[dimensionN] array_name = new data_type[size1][size2].[sizeN];
Где Data_type — это тип элементов в массиве. Может быть примитивным или ссылочным (классом). Количество пар скобок с dimension внутри — размерность массива (в нашем случае — N). array_name — название массива size1...sizN — количество элементов в каждом из измерений массива. Объявление многомерных массивов:
int[][] twoDimArray; //двумерный массив
String[][][] threeDimArray; //трёхмерный массив
double[][][][][] fiveDimArray; // пятимерный массив
Возможно, всё это выглядит очень абстрактно, поэтому теперь перейдём к конкретным проявлениям многомерных массивах — двумерным и трёхмерным. Дело в том, что Java-разработчики иногда пользуются двумерными массивами, гораздо реже — трёхмерными, ну а массивы ещё большей размерности — чрезвычайно редки и с большой долей вероятности вы с ними не будете сталкиваться.

Многомерные массивы в курсе JavaRush

На JavaRush к обычным массивам приступают на 7 уровне квеста Java Syntax и далее по ходу курса они встречаются неоднократно. Иногда на протяжении курса попадаются задачи на двумерные массивы (или такие, которые можно решить с их помощью). А ещё двумерные массивы использованы в движке игр специального раздела “Игры на JavaRush”. Если вы ещё там не были, загляните и создайте пару-тройку игр. К условиям прилагаются подробные инструкции, и это послужит прекрасной тренировкой навыков программирования. Трёхмерный массив можно обнаружить в игре Space Invaders. Через него задаётся набор фреймов для анимации (и каждый из этих фреймов — двумерный массив). Если вы уже прошли квест JavaSyntax или просто уверенно себя чувствуете в программировании на Java, попробуйте написать собственную версию этой классической игры.

Что такое двумерный массив?

Двумерный массив в Java — это массив массивов, то есть в каждой его ячейке находится ссылка на некий массив. Но гораздо проще его представить в виде таблицы, у которой есть количество строк (первое измерение) и количество столбцов (второе измерение). Двумерный массив, у которого все строки имеют равное количество элементов, называется прямоугольным.

Объявление, создание и инициализация двумерных массивов

Объявление и создание двумерного массива практически такая же, как и в случае одномерного:
int[][] twoDimArray = new int[3][4];
Этот массив имеет 3 строки и 4 столбца. Размер прямоугольного двумерного массива (они могут быть и не прямоугольными, об этом — чуть ниже), то есть общее количество элементов можно определить, перемножив количество строк на количество столбцов. Сейчас он проинициализирован (заполнен) значениями по умолчанию. То есть — нулями. Давайте заполним его нужными нам значениями.
twoDimArray[0][0] = 5;//записали значение 5 в ячейку на пересечении нулевой строки и нулевого столбца
twoDimArray[0][1] = 7; //записали значение 7 в ячейку на пересечении нулевой строки и первого столбца
twoDimArray[0][2]  = 3;
twoDimArray[0][3] = 17;
twoDimArray[1][0] = 7;
twoDimArray[1][1] = 0;
twoDimArray[1][2] = 1;
twoDimArray[1][3] = 12;
twoDimArray[2][0] = 8;
twoDimArray[2][1] = 1;
twoDimArray[2][2] = 2;
twoDimArray[2][3] = 3;
Как и в случае с одномерными массивами, можно провести процедуру инициализации быстрее:
int [][] twoDimArray = {{5,7,3,17}, {7,0,1,12}, {8,1,2,3}};
И в том, и в другом случае мы получим двумерный массив с тремя строками и четырьмя столбцами, заполненный целыми числами.

Вывод двумерного массива на экран

Эту операцию логичнее всего делать так: сначала выводим нулевую строку поэлементно, затем — вторую и так далее. Чаще всего на Java вывод двумерного массива реализуют с помощью двух вложенных циклов.
int [][] twoDimArray = {{5,7,3,17}, {7,0,1,12}, {8,1,2,3}};//объявили массив и заполнили его элементами
for (int i = 0; i < 3; i++) {  //идём по строкам
            for (int j = 0; j < 4; j++) {//идём по столбцам
                System.out.print(" " + twoDimArray[i][j] + " "); //вывод элемента
            }
            System.out.println();//перенос строки ради визуального сохранения табличной формы
        }

Быстрый вывод двумерного массива

Самый короткий способ вывести список элементов двумерного массива на экран — применение метода deepToString класса Arrays. Пример:
int[][] myArray = {{18,28,18},{28,45,90},{45,3,14}};
System.out.printLn(Arrays.deepToString(myArray));
Результат работы программы — следующий вывод: [[18, 28, 18], [28, 45, 90], [45, 3, 14]]

“Длины” двумерного массива

Чтобы получить длину одномерного массива (то есть, количество элементов в нём) можно использовать переменную length. То есть, если мы определим массив int a[] = {1,2,3}, то операция a.length возвращает 3. А что, если эту же процедуру применить к нашему двумерному массиву?
int [][] twoDimArray = {{5,7,3,17}, {7,0,1,12}, {8,1,2,3}};
System.out.println(twoDimArray.length);
Вывод: 3 Таким образом, эта операция выводит количество строк в массиве. А как получить количество столбцов? Если мы имеем дело с прямоугольными двумерными массивами (то есть такими, у которых все строки одинаковой длины), то можно применить операцию twoDimArray[0].length или вместо нулевого элемента (по сути — нулевой строки) — любой другой существующий. Мы так можем поступить, потому что в Java двумерный массив — это массив массивов, так что нулевой элемент twoDimArray[0] — это массив длины 4. Можете проверить это самостоятельно.

Пример использования двумерного массива: шахматная доска

Двумерные массивы можно использовать для создания любого конечного двумерного поля, например, в играх и в частности — в шахматах. Шахматную доску легко представить в виде двумерного массива. К этому можно “прикрутить” графику, а пока что — давайте зададим шахматное поле с помощью символов и выведем его в консоль. Нижняя левая клетка шахматной доски окрашена в чёрный цвет, следующая за ней — в белый, как и та, что над ней. Итак, цвет меняется каждый раз при переходе на соседнюю по стороне ячейку. Чтобы задавать шахматную расцветку не вручную, а с помощью алгоритма, можно использовать проверку на чётность: если сумма индекса строки и столбца — чётная или нуль, то клетка будет белой, иначе — чёрной. Для этой проверки в алгоритме используем оператор остатка от деления %. Поскольку мы работаем не с графикой, а с символами, обозначим белую клетку буквой W (white), а чёрную — буквой B (black).
//задаём шахматную доску двумерным массивом
String [][] chessBoard = new String[8][8];
        for (int i = 0; i< chessBoard.length; i++) {
            for (int j = 0; j < chessBoard[0].length; j++) {
                if ((i + j) % 2 == 0) chessBoard[i][j] = "W";
                else chessBoard[i][j] = "B";
            }
        }
Вывод программы получается такой: W B W B W B W B B W B W B W B W W B W B W B W B B W B W B W B W W B W B W B W B B W B W B W B W W B W B W B W B B W B W B W B W Всё как на реальной шахматной доске, можете проверить. А теперь давайте напишем метод для правильной нумерации ячеек не на языке массивов, а на “шахматном” языке. Нижняя левая ячейка на доске называется А1, в то время как в нашем массиве это — chessBoard[7][0]. Сопоставим каждой паре индексов двумерного массива их “шахматный” эквивалент. Для этого используем две строки — "abcdefgh" и "87654321" (в обратном порядке — для простоты, чтобы шахматная 8 соответствовала нулевому столбцу).
public static String chessBoardCoord(int a, int b) {
            String letters = "abcdefgh";
            String numbers = "87654321";
            if ((a > 7)|| (b>7)) return null; //если номер за пределами доски, возвращаем значение по умолчанию - null
            else return (Character.toString(letters.charAt(a)) + numbers.charAt(b)); /*charAt - метод, с помощью которого мы извлекаем из строки элемент под переданным номером, здесь - под номерами a и b. Character.toString - метод, который переводит полученный символ в строку*/
        }
Теперь выведем в каждой ячейке не только её цвет, но также её номер, используя метод chessBoardCoord
String [][] chessBoard = new String[8][8];
        for (int i = 0; i < chessBoard.length; i++) {
            for (int j = 0; j < chessBoard[0].length; j++) {
                if ((i + j) % 2 == 0) chessBoard[i][j] = "W" + chessBoardCoord(j,i);
                else chessBoard[i][j] = "B"+ chessBoardCoord(j,i);
            }
        }

            for (int i = 0; i < chessBoard.length; i++) {
                for (int j = 0; j < chessBoard[0].length; j++) {
                    System.out.print(" " + chessBoard[i][j] + " ");
                }
                System.out.println();
            }
Вывод программы: Wa8 Bb8 Wc8 Bd8 We8 Bf8 Wg8 Bh8 Ba7 Wb7 Bc7 Wd7 Be7 Wf7 Bg7 Wh7 Wa6 Bb6 Wc6 Bd6 We6 Bf6 Wg6 Bh6 Ba5 Wb5 Bc5 Wd5 Be5 Wf5 Bg5 Wh5 Wa4 Bb4 Wc4 Bd4 We4 Bf4 Wg4 Bh4 Ba3 Wb3 Bc3 Wd3 Be3 Wf3 Bg3 Wh3 Wa2 Bb2 Wc2 Bd2 We2 Bf2 Wg2 Bh2 Ba1 Wb1 Bc1 Wd1 Be1 Wf1 Bg1 Wh1 Где We2 означает белую клетку с номером e2.

Пример использования двумерного массива: умножение матриц

Внимание! Этот пример требует элементарных знаний о матрицах. Здесь о них будет рассказано совсем немного, и эта информация рассчитана скорее на тех, кто изучал, но несколько подзабыл матричную арифметику. Тем не менее, эти знания можно почерпнуть из открытых источников, в частности — из статьи в Википедии. Это удачный пример использования двумерных массивов, но без него можно двигаться дальше. Так что если он вам сейчас кажется непонятным с точки зрения математики, и вы не слишком-то хотите углубляться в неё, смело пропускайте его. Если вы учили начала линейной алгебры, возможно, вы узнали в прямоугольных массивах прямоугольные матрицы. Где а11, а12… aNN — некоторые числа. На рисунке матрица даже не прямоугольная, а квадратная (число строк равно числу столбцов, но это не всегда так). В реальной жизни с такими матрицами сталкиваются редко, а вот в программировании и компьютерных науках — очень часто. В частности, их используют в компьютерной графике и игровых движках. Скажем, поворот какого-либо объекта на экране на любой угол можно программировать с помощью матрицы поворотов. В двумерном пространстве матрица поворотов выглядит так: Где тета — угол, на который нужно развернуть объект. Одинаковые по размерности матрицы можно складывать между собой, при этом сложение идёт поэлементно (складываем элементы с одинаковыми индексами). А вот операция умножения матриц уже менее привычная. Для начала, матрицы можно перемножить и получить в матрицу-результат только в том случае, если количество столбцов первой матрицы совпадает с количеством строк второй. Матрица-результат будет иметь столько же строк, как в первой, и столько же столбцов, как во второй. Умножение производится следующим образом. Пусть у нас есть матрица a[l][m] и b[m][n]. В результате их перемножения мы должны получить матрицу c[l][n]. Для получения элемента c[0][0] матрицы-произведения, нужно нулевой элемент нулевой строки первой матрицы a[0][0] перемножить на нулевой элемент второй матрицы, затем первый элемент первой строки первой матрицы умножить на первый элемент первого столбца второй матрицы и так далее, после чего все получившиеся произведения сложить.
a[0][0]*b[0][0] + a[0][1]*b[1][0] + … + a[0][m-1]*b[m-1][0]
Для получения второго элемента первой строки матрицы-результата проделываем ту же процедуру со второй строкой
a[1][0]*b[0][0] + a[1][1]*b[0][1] + … + a[0][m-1]*b[m-1][0]
И так до конца строки. Затем переходим на следующую строку и так пока они у нас не закончатся. То есть мы перемножаем строки первой матрицы со столбцами второй матрицы. Ниже — код для перемножения матриц. Можете его дополнить проверкой на соблюдение упомянутого выше условия о количестве строк и столбцов.
//объявляем две матрицы
int [][] twoDimArray1 = {{1,0,0,0},{0,1,0,0},{0,0,0,0}};
int[][] twoDimArray2 = {{1,2,3},{1,1,1},{0,0,0},{2,1,0}};

//процесс умножения матриц
int[][]twoDimArray3 = new int [twoDimArray1.length][twoDimArray2[0].length];
        for (int i=0; i<twoDimArray3[0].length; i++)
            for (int j=0; j<twoDimArray3.length; j++)
                for (k=0; k<twoDimArray1[0].length; k++)
                              twoDimArray3[i][j] = twoDimArray3[i][j] + twoDimArray1[i][k] * twoDimArray2[k][j];

//вывод на экран
        for (int i = 0; i < twoDimArray3.length; i++) {
            for (int j = 0; j < twoDimArray3[0].length; j++) {
                System.out.print(" " + twoDimArray3[i][j] + " ");
            }
            System.out.println();
        }
Программа выводит следующий результат: 1 2 3 1 1 1 0 0 0

Непрямоугольные двумерные массивы

Поскольку в Java двумерные массивы — это массивы массивов, каждый из внутренних массивов может быть разной длины. При этом создавая массив мы можем указать только количество строк и не указывать количество столбцов (то есть, по сути, длину этих самых строк). Рассмотрим пример.
//объявляем и создаём массив, указывая только количество строк
int [][] twoDimArray = new int[5][];

//инициализируем массив, заполняя его массивами разной длины
        twoDimArray[0] = new int[]{1, 2, 3, 4, 5};
        twoDimArray[1] = new int[]{1,2,3,4};
        twoDimArray[2] = new int[]{1,2,3};
        twoDimArray[3] = new int[]{1,2};
        twoDimArray[4] = new int[]{1};
//выведем получившийся непрямоугольный двумерный массив на экран
        int k = 0;
        for (int i = 0; i < twoDimArray.length; i++) {
            for (int j = 0; j < twoDimArray[k].length; j++) {
                System.out.print(" " + twoDimArray[i][j] + " ");
            }
            k++;
            System.out.println();
        }
Вывод программы: 1 2 3 4 5 1 2 3 4 1 2 3 1 2 1 Таким образом нулевая строка нашего массива содержит массив {1,2,3,4,5}, а четвёртая — массив {1}.

Трёхмерные массивы в Java

Следуя здравому смыслу и логике языка Java, трёхмерный массив можно назвать массив массивов массивов или массивом, каждым элементом которого является двумерный массив. Причём эти двумерные массивы могут быть разными. Пример:
//создаём трёхмерный массив, состоящий из двух двухмерных массивов
int[][][] threeDimArr = new int[2][][];
//создаём первый двумерный массив трёхмерного массива размерностью 5х2
        threeDimArr[0] = new int[5][2];
//создаём второй двумерный массив трёхмерного массива размерностью 1х1
        threeDimArr[1] = new int[1][1];
Но чаще на практике встречаются трёхмерные массивы у которых все три величины определены сразу, аналог прямоугольных двумерных массивов. Как мы уже упоминали, трёхмерные и более массивы используются очень редко. Тем не менее, с помощью трёхмерного массива можно запрограммировать что-то интересное. Например, многоэтажную парковку. Каждый этаж можно считать двумерным массивом, а паркоместо — конкретным элементом трёхмерного массива. Каждый элемент такого массива можно представить типом boolean со значением false, если место свободно и true, если место занято.
//задаем булевский трёхмерный массив. На этой парковке есть 3 этажа, на каждом из которых можно поместить 2х5 = 10 машин. По умолчанию все ячейки пусты (false)
boolean[][][] parkingLot = new boolean[3][2][5];
//приехало две машины и припарковались на нулевом этаже в ячейке [1][0] и [1][3]
        parkingLot[0][1][0] = true;
        parkingLot[0][1][3] = true;

//Выведем массив в консоль
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 2; j++) {
                for (int k = 0; k < 5; k++) {
                    System.out.print("arr[" + i + "][" + j + "][" + k + "] = " + parkingLot[i][j][k] + "\t");

                }
                System.out.println();
            }
        }

Многомерные массивы в реальной работе Java-программиста

В реальности большинство Java-разработчиков сталкиваются с многомерными массивами не слишком часто. Тем не менее, есть ряд задач, для которых эта структура данных подходит очень даже неплохо.

  1. Для тестов и задания матриц в качестве констант, чтобы проверить тот или иной алгоритм.
  2. Иногда многомерные массивы используют для нейросетей.
  3. Многомерные массивы подходят для работы архиваторов.
  4. Работа с изображениями.

Интересные задачи на двумерные и трёхмерные массивы

Вы достаточно знаете о многомерных массивах в Java и, если чувствуете в себе силы, можете попробовать решить какую-то из задач ниже. Они непростые, но интересные. Крестики-нолики. Задайте поле 3х3, создайте двух игроков, которые ходят поочередно. Изначально поле пустое, и в каждое из пустых полей первый игрок может ставить крестик, а второй нолик. Выигрывает тот, кто первым соберёт три крестика или три нолика расположенных в одну строку, один столбец или по диагонали.

Что ещё почитать

Игра на Java для начинающих

Муравей Лэнгтона. Есть некое поле, разбитое на клетки (двумерный массив), выкрашенные в чёрный или белый цвет (можно задать случайной функцией). В одной из клеток случайным образом материализуется “муравей”, который на каждом шаге может передвигаться в одном из четырёх направлений на соседнюю клетку по горизонтали или вертикали. Правила передвижения муравья:
  • На чёрной клетке муравей должен повернуть на 90° влево, изменить цвет своей клетки на белый, затем сделать шаг вперед на следующую клетку.
  • На белой клетке муравей поворачивается на 90° вправо и меняет цвет своей клетки на чёрный, затем делает шаг вперед на следующую клетку.
Напишите метод, который просчитывает итерацию на шаге номер n при заданной начальной позиции муравья. Поле можно заполнить случайным образом нулями и единицами (или обозначить их буквами W и B, как мы делали в примере о шахматной доске). Также нужны ещё два параметра — горизонтальная и вертикальная позиция муравья, а также его направление на данном шаге (север, юг, запад, восток), при этом по умолчанию муравей смотрит на север. Кубик Рубика можно попытаться смоделировать с помощью трёхмерных массивов. Стандартный кубик Рубика имеет 6 граней, и каждая из них является трехмерным массивов цветных квадратов Color[][][] rubik = new Color[6][3][3]. Однако реализация кубика Рубика — задача нетривиальная.

Полезные материалы о массивах

Массивам (преимущественно, одномерным, поскольку они гораздо чаще применяются на практике) посвящено много статей на JavaRush. Обратите на них внимание.
  1. Массивы в Java — о массивах для новичков с примерами
  2. Кое-что о массивах — хорошая подробная статья о массивах
  3. Класс Arrays и его использование — в статье описаны некоторые методы класса Array
  4. Массивы первая лекция JavaRush, посвящённая массивам.
  5. Возвращайте массив нулевой длины, а не null — автор “Эффекктивного программирования” Джошуа Блох рассказывает о том, как лучше возвращать пустые массивы