Multidimensional Arrays
A multidimensional array stores data in more than one dimension: a table of rows and columns, a grid of cells, a stack of tables. Java implements these as arrays of arrays. A two-dimensional int[][] is an array whose elements are themselves int[] arrays. This design is flexible (rows can have different lengths), and understanding it explains how such arrays are created, indexed, and looped over.
Declaring and Creating a 2D Array
int[][] grid = new int[3][4]; // 3 rows, 4 columns, all 0
Read the type int[][] as "array of int[]". The expression new int[3][4] creates:
grid ──► [ row0, row1, row2 ] an array of 3 references
│ │ │
▼ ▼ ▼
[0,0,0,0] [0,0,0,0] [0,0,0,0] three separate int[4] arrays
There are four objects here: one outer array and three row arrays. The outer array holds references to the rows.
With an initializer
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Each inner brace group becomes one row. The dimensions are taken from the data.
Accessing Elements
Use one index per dimension: first the row, then the column.
int center = matrix[1][1]; // 5
matrix[2][0] = 70; // row 2, column 0
int[] secondRow = matrix[1]; // the whole row: {4, 5, 6}
matrix[1] is an ordinary int[], so you can pass a row to any method that accepts a one-dimensional array:
System.out.println(Arrays.toString(matrix[1])); // [4, 5, 6]
Each index is checked separately. matrix[3][0] fails on the row index; matrix[0][3] fails on the column index; both throw ArrayIndexOutOfBoundsException.
Dimensions: length at Each Level
int[][] grid = new int[3][4];
System.out.println(grid.length); // 3: number of rows
System.out.println(grid[0].length); // 4: columns in row 0
There is no single "column count" stored for the whole array. Each row knows its own length.
Looping Through a 2D Array
Nested loops visit every cell. The outer loop goes over rows; the inner loop goes over the columns of the current row:
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
for (int r = 0; r < matrix.length; r++) {
for (int c = 0; c < matrix[r].length; c++) {
System.out.print(matrix[r][c] + " ");
}
System.out.println();
}
Output:
1 2 3
4 5 6
7 8 9
The inner condition uses matrix[r].length, the length of the current row, so the same loop also works when rows differ in length.
When indexes are not needed, nested for-each loops are shorter:
int sum = 0;
for (int[] row : matrix) {
for (int value : row) {
sum += value;
}
}
System.out.println(sum); // 45
Printing a 2D Array
Arrays.toString prints only the references of the inner arrays. Use Arrays.deepToString for nested arrays:
System.out.println(Arrays.toString(matrix));
// [[I@7a81197d, [I@5ca881b5, [I@24d46ca6]
System.out.println(Arrays.deepToString(matrix));
// [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Similarly, use Arrays.deepEquals to compare nested arrays by content.
Row-by-Row and Column-by-Column Operations
Summing a row uses one fixed row index; summing a column uses one fixed column index across all rows:
int[][] sales = {
{120, 135, 150}, // region 0: Jan, Feb, Mar
{200, 180, 210}, // region 1
{90, 110, 95} // region 2
};
int region1Total = 0;
for (int m = 0; m < sales[1].length; m++) {
region1Total += sales[1][m];
}
int febTotal = 0;
for (int r = 0; r < sales.length; r++) {
febTotal += sales[r][1];
}
System.out.println(region1Total + " " + febTotal); // 590 425
Row operations are naturally fast in Java because each row is a contiguous array. Column operations jump between separate row arrays in memory. For large numeric work, looping row by row in the outer loop is noticeably faster than column by column.
Jagged Arrays
Because a 2D array is an array of arrays, rows can have different lengths. Create the outer array with only the first dimension, then allocate each row separately:
int[][] triangle = new int[5][]; // 5 rows, not yet allocated (all null)
for (int r = 0; r < triangle.length; r++) {
triangle[r] = new int[r + 1]; // row r has r + 1 elements
triangle[r][0] = 1;
triangle[r][r] = 1;
for (int c = 1; c < r; c++) {
triangle[r][c] = triangle[r - 1][c - 1] + triangle[r - 1][c];
}
}
for (int[] row : triangle) {
System.out.println(Arrays.toString(row));
}
Output (Pascal's triangle):
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
new int[5][] creates the outer array with five null slots. Accessing triangle[r][0] before assigning triangle[r] would throw NullPointerException. Jagged arrays save memory when rows genuinely differ in size, such as a list of students per class, or the lower half of a symmetric distance table.
You can only leave out trailing dimensions: new int[5][] is legal, new int[][5] is not.
Copying a 2D Array
clone() and Arrays.copyOf on the outer array copy only the row references, so the copy shares its rows with the original:
int[][] original = {{1, 2}, {3, 4}};
int[][] shallow = original.clone();
shallow[0][0] = 99;
System.out.println(original[0][0]); // 99: same row array
A deep copy must copy each row:
int[][] deep = new int[original.length][];
for (int r = 0; r < original.length; r++) {
deep[r] = original[r].clone();
}
deep[0][0] = 1;
System.out.println(original[0][0]); // still 99
Three or More Dimensions
The same idea extends to any number of dimensions:
// [building][floor][room]
int[][][] occupancy = new int[2][3][4];
occupancy[1][2][3] = 2; // building 1, floor 2, room 3 has 2 people
int total = 0;
for (int[][] building : occupancy) {
for (int[] floor : building) {
for (int room : floor) {
total += room;
}
}
}
System.out.println(total); // 2
Arrays beyond two or three dimensions become hard to read. When dimensions have meanings (building, floor, room), a small class or record hierarchy often expresses the data more clearly.
Common Uses
| Use | Typical shape |
|---|---|
| Tables of numbers (sales by region and month, marks by student and subject) | int[][], double[][] |
| Game boards (tic-tac-toe, chess, Sudoku) | char[][], int[][] |
| Images (pixel grids, one row per scan line) | int[height][width] |
| Matrices in math and graphics | double[rows][cols] |
| Distance or adjacency tables between locations | int[n][n] |
Example: a tic-tac-toe winner check
static boolean wins(char[][] b, char p) {
for (int i = 0; i < 3; i++) {
if (b[i][0] == p && b[i][1] == p && b[i][2] == p) return true; // row i
if (b[0][i] == p && b[1][i] == p && b[2][i] == p) return true; // column i
}
return (b[0][0] == p && b[1][1] == p && b[2][2] == p) // diagonals
|| (b[0][2] == p && b[1][1] == p && b[2][0] == p);
}
char[][] board = {
{'X', 'O', 'X'},
{'O', 'X', 'O'},
{' ', ' ', 'X'}
};
System.out.println(wins(board, 'X')); // true: main diagonal
Practical Example: Gradebook
import java.util.Arrays;
public class Gradebook {
public static void main(String[] args) {
String[] students = {"Ada", "Linus", "Grace"};
String[] subjects = {"Java", "SQL", "Docker", "Bash"};
int[][] marks = {
{88, 92, 75, 81},
{67, 74, 90, 58},
{95, 85, 88, 92}
};
System.out.printf("%-8s", "");
for (String sub : subjects) System.out.printf("%7s", sub);
System.out.printf("%7s%n", "Avg");
for (int r = 0; r < marks.length; r++) {
System.out.printf("%-8s", students[r]);
int sum = 0;
for (int c = 0; c < marks[r].length; c++) {
System.out.printf("%7d", marks[r][c]);
sum += marks[r][c];
}
System.out.printf("%7.1f%n", (double) sum / marks[r].length);
}
System.out.printf("%-8s", "Top");
for (int c = 0; c < subjects.length; c++) {
int best = 0;
for (int r = 1; r < marks.length; r++) {
if (marks[r][c] > marks[best][c]) best = r;
}
System.out.printf("%7s", students[best]);
}
System.out.println();
int[][] transposed = new int[subjects.length][students.length];
for (int r = 0; r < marks.length; r++) {
for (int c = 0; c < marks[r].length; c++) {
transposed[c][r] = marks[r][c];
}
}
System.out.println("By subject: " + Arrays.deepToString(transposed));
}
}
Output:
Java SQL Docker Bash Avg
Ada 88 92 75 81 84.0
Linus 67 74 90 58 72.3
Grace 95 85 88 92 90.0
Top Grace Ada Linus Grace
By subject: [[88, 67, 95], [92, 74, 85], [75, 90, 88], [81, 58, 92]]
Rows represent students and columns represent subjects, with the parallel arrays students and subjects supplying the labels. The row loop computes each student's average by fixing r and moving across c. The "Top" line swaps the roles: it fixes a column c and moves down the rows to find the highest mark in that subject. The transpose creates a new array with the dimensions reversed ([subjects][students]) and copies marks[r][c] into transposed[c][r], turning a per-student table into a per-subject one.