6.4 Multidimensional Arrays and Shape Validation
Prerequisites: One-dimensional arrays, loops, and method contracts Estimated time: 25 minutes You are ready to continue when: You can safely iterate over rectangular, empty, uneven, and null rows and validate the shape of a matrix before matrix algorithms
A one-dimensional array uses one index. Chessboards, tables, and pixel grids need row and column coordinates. Java uses arrays of arrays to express this structure.
1. The component type of int[][] is int[]
int[][] grid = new int[3][4];This creates an outer array of length 3, and for each component, it creates a row of length 4 int[]. All integer components are initialized to the default value of 0.
int[][] grid = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
System.out.println(grid[1][2]); // 7The expression is understood in two steps: grid[1] obtains the second int[], and then [2] reads the third integer in that row.
Java specifies array object and index semantics. Programs should not assume, based on the source code's int[3][4], that all rows are physically contiguous in memory.
2. Using the Current Row Length During Iteration
for (int row = 0; row < grid.length; row++) {
for (int column = 0; column < grid[row].length; column++) {
System.out.print(grid[row][column] + " ");
}
System.out.println();
}grid.length is the length of the outer array; grid[row].length is the length of the current row. Even if the current example is rectangular, do not hardcode grid[0].length as the inner condition, otherwise it can lead to out-of-bounds errors when switching to irregular arrays.
When reading only, you can use the enhanced for:
for (int[] row : grid) {
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}3. Outer Arrays Can Be Created in Stages
int[][] seats = new int[3][];
System.out.println(seats[0]); // null
seats[0] = new int[4];
seats[1] = new int[2];
seats[2] = new int[5];new int[3][] creates only three row-reference components, which are initially null. Each row can be created into a different length later, which is called an irregular or jagged array.
int[][] triangle = {
{},
{1},
{2, 3},
null
};If the input allows null rows, the traversal must first handle:
static long sumRows(int[][] values) {
long total = 0L;
for (int[] row : values) {
if (row == null) {
continue;
}
for (int value : row) {
total = Math.addExact(total, value);
}
}
return total;
}Alternatively, null rows can be treated as invalid input and an exception can be thrown. The key is to write it into the contract, not let the algorithm decide by chance.
4. Validate Shape Before Running a Matrix Algorithm
Algorithms such as matrix transposition and matrix multiplication typically require that each row has the same length and is not null. A validation function can return the number of columns:
static int requireRectangular(int[][] matrix) {
if (matrix == null) {
throw new NullPointerException("matrix");
}
if (matrix.length == 0) {
return 0;
}
if (matrix[0] == null) {
throw new IllegalArgumentException("null row: 0");
}
int columns = matrix[0].length;
for (int row = 1; row < matrix.length; row++) {
if (matrix[row] == null || matrix[row].length != columns) {
throw new IllegalArgumentException("not rectangular at row " + row);
}
}
return columns;
}An empty outer array has no first row, so this text treats it as 0 columns. Other APIs can also require the caller to provide the number of columns as long as the contract is consistent.
Validate before transposing:
static int[][] transpose(int[][] matrix) {
int columns = requireRectangular(matrix);
int[][] result = new int[columns][matrix.length];
for (int row = 0; row < matrix.length; row++) {
for (int column = 0; column < columns; column++) {
result[column][row] = matrix[row][column];
}
}
return result;
}A 3×4 input produces 4×3 output. A 0×0 input stays empty. Transposing 1×0 also produces an empty outer array, which cannot retain the fact that it has one column; transposing again will not recover the original empty row. Preserve row and column counts separately when zero-sized dimensions must round-trip.
5. Output and Comparison of Nested Arrays
import java.util.Arrays;
int[][] a = {{1, 2}, {3, 4}};
int[][] b = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepToString(a));
// [[1, 2], [3, 4]]
System.out.println(Arrays.deepEquals(a, b));
// trueArrays.toString(a) only formats the outer component, so each line still displays as a string of array objects. Nested arrays require deepToString; comparisons based on nested content use deepEquals. For self-referential object arrays, the contracts differ: deepToString marks circular references with [...], whereas deepEquals leaves behavior undefined for arrays that contain themselves directly or indirectly. It is not a general graph comparator.
Common Issues
| Phenomenon | Cause | Repair Direction |
|---|---|---|
grid[row].length Null Pointer | Current row is null | Reject or skip null rows |
| A row is out of bounds | Assumes all rows are of equal length | Use the length of the current row or validate the rectangle first |
| Transposed result has rows and columns reversed | Target index is written in reverse | Write result[column][row] to the target |
Output contains [I@... | Only formatted the outer layer | Use Arrays.deepToString |
Arrays.equals Returns false | It compares references of outer components | Nested content uses deepEquals |
Before Moving On
Let the traversal function sequentially accept a rectangular array, an empty outer layer array, an empty row, an uneven row, and null row, clearly determining whether each input is accepted, skipped, or rejected. Validate the shape before transposing, and distinguish toString, deepToString, equals, and deepEquals when outputting and comparing nested content.