Two-dimensional arrays
Java has no true 2D array. It has arrays whose elements are arrays.
int[][] grid = new int[3][4]; // 3 rows, 4 columns
grid[1][2] = 7;
grid is an array of three references, each pointing at an int[4]. That indirection explains everything else on this page.
Rows and columns
grid.length // 3 — the number of rows
grid[0].length // 4 — the length of row 0
There is no grid.width. Ask a row how long it is, because rows need not agree.
Ragged arrays
Since each row is a separate array, rows can differ in length:
int[][] triangle = new int[4][];
for (int i = 0; i < 4; i++) {
triangle[i] = new int[i + 1];
}
That produces rows of length 1, 2, 3, 4. Legal and occasionally useful — a triangular matrix, or lines of a file.
The consequence is that grid[i].length must be read per row rather than assumed:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
...
}
}
Writing grid[0].length in the inner condition works for rectangular arrays and throws on ragged ones.
Literals
int[][] m = {
{1, 2, 3},
{4, 5, 6}
};
Iterating with for-each
for (int[] row : grid) {
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}
Cleaner when you do not need the indices. You do need them if you are writing values back or working with the position.
Printing
Arrays.toString(grid) prints the row references, not the contents. Use:
System.out.println(Arrays.deepToString(grid));
Likewise Arrays.deepEquals rather than Arrays.equals for comparing.
Row order matters for speed
A row is contiguous in memory; separate rows may be anywhere. Iterating row by row is friendlier to the cache than column by column. On small grids this is invisible; on large numeric work it is a measurable difference for the same operations.