6.4 多维数组与矩形校验
前置知识:一维数组、循环和方法契约
预计时间:25 分钟
完成标志:能安全遍历矩形、空行、不等长行和 null 行,并能在矩阵算法前验证形状
一维数组按单个索引定位。棋盘、表格和像素网格还需要行、列两个坐标。Java 用数组的数组表达这种结构。
1. int[][] 的组件类型是 int[]
int[][] grid = new int[3][4];这会创建一个长度为 3 的外层数组,并为每个组件创建一个长度为 4 的 int[] 行。所有整数组件先取默认值 0。
int[][] grid = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
System.out.println(grid[1][2]); // 7表达式分两步理解:grid[1] 得到第二个 int[],再由 [2] 读取该行第三个整数。
Java 规定的是数组对象和索引语义。程序不应根据源码中的 int[3][4] 假设所有行在物理内存中连续排列。
2. 遍历时使用当前行的长度
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 是外层数组长度;grid[row].length 是当前行长度。即使当前示例是矩形,也不要把内层条件写死成 grid[0].length,否则换成不规则数组后容易越界。
只读时可以使用增强 for:
for (int[] row : grid) {
for (int value : row) {
System.out.print(value + " ");
}
System.out.println();
}3. 外层数组可以分阶段创建
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][] 只创建三个行引用组件,它们最初都是 null。每行可在之后创建成不同长度,这叫不规则数组或锯齿数组。
int[][] triangle = {
{},
{1},
{2, 3},
null
};若输入允许 null 行,遍历必须先处理:
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;
}也可以把 null 行视为非法输入并抛异常。关键是写进契约,不要让算法偶然决定。
4. 需要矩形时先验证形状
矩阵转置、矩阵乘法等算法通常要求每行长度相同且不为 null。验证函数可以返回列数:
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;
}空的外层数组没有第一行,因此本文把它视为 0 列。其他 API 也可以要求调用者额外提供列数,只要契约一致。
验证后再转置:
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;
}输入为 3×4 时结果为 4×3。输入为 0×0 时结果仍为空数组。1×0 转置后也得到空外层数组,而这个结构无法保存“有 1 列”的信息;再次转置不会恢复原来的那一个空行。需要完整保留零维形状时,应把行列数另存为元数据。
5. 嵌套数组的输出与比较
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) 只格式化外层组件,因此每行仍显示为数组对象字符串。嵌套数组要使用 deepToString;按嵌套内容比较则使用 deepEquals。自引用对象数组需要分别看契约:deepToString 用 [...] 标记循环引用,而 deepEquals 对直接或间接包含自身的数组不定义结果,不能当作通用图比较器。
常见故障
| 现象 | 原因 | 修复方向 |
|---|---|---|
grid[row].length 空指针 | 当前行是 null | 拒绝或跳过 null 行 |
| 某一行越界 | 假设所有行等长 | 使用当前行长度或先验证矩形 |
| 转置结果行列颠倒 | 目标索引写反 | 目标写 result[column][row] |
输出出现 [I@... | 只格式化了外层 | 使用 Arrays.deepToString |
Arrays.equals 返回 false | 它比较外层组件引用 | 嵌套内容用 deepEquals |
离开这一页前
让遍历函数依次接收矩形数组、空外层数组、空行、不等长行和 null 行,明确每种输入是接受、跳过还是拒绝。转置前先验证形状,输出与比较嵌套内容时再区分 toString、deepToString、equals 与 deepEquals。