首页 / Java 入门教程 / 多维数组

Java 入门教程

多维数组

本教程共 100 篇 · 第 26 篇 · 更新于 2026-08-05 · 约 6 分钟阅读

JavaJava 入门教程二维数组多维数组不规则数组

26. 多维数组

本节目标:理解二维数组就是”数组的数组”,看清它在内存里的真实样子,并学会处理每行长度不同的不规则数组。

一维数组排成一排。要表示”表格”——比如 3 行 4 列的矩阵——就得用二维数组。很多初学者以为二维数组是块整齐的矩形内存,其实在 Java 里,二维数组本质是”数组里面再装数组”。这个认知对了,后面所有坑都好理解。

声明与初始化

int[][] matrix = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};
```java

`int[][]` 读作"int 的二维数组"。外层每个元素又是一个一维数组。也可以动态创建:

```java
int[][] m = new int[3][4];   // 3 行,每行 4 列,默认全 0

这种一次给定行和列的写法,会同时把每一行都 new 好,用起来最方便。

Note

new int[3][4] 也可以写成 new int[3][] 然后逐行 new。后者能造出每行长度不一样的不规则数组,下面会讲到。

内存里的真实样子

二维数组不是一块整齐的矩形内存。matrix 这个变量指向一个长度为 3 的”行数组”,而行数组的每个元素分别指向各自的一维数组。

matrix ──> [  ref0,  ref1,  ref2  ]
             |      |      |
             v      v      v
            [1,2,3,4] [5,6,7,8] [9,10,11,12]
```java

所以访问元素是 `matrix[行][列]`:

```java
System.out.println(matrix[0][1]);   // 2,第 0 行第 1 列
matrix[1][2] = 99;                  // 改第 1 行第 2 列

理解这个”两层引用”的结构,是看懂不规则数组和遍历越界的前提。

不规则数组

因为”每行是一个独立数组”,每行长度可以不一样。这叫不规则(锯齿)数组

int[][] tri = new int[3][];     // 先定 3 行,列数后补
tri[0] = new int[1];
tri[1] = new int[2];
tri[2] = new int[3];
tri[1][1] = 7;
```bash

第一行 1 列、第二行 2 列、第三行 3 列,像三角形。这种灵活度是 C 语言那种"真二维数组"没有的。很多真实数据就是不规则的,比如一个班级里每个学生选的课程数不同。

> [!TIP]
> 动态建不规则数组时,记得先给 `tri[i]` 各自 `new` 一个一维数组,否则它还是 null,访问会抛 `NullPointerException`。

## 遍历二维数组

外层走行,内层走列:

```java
for (int i = 0; i < matrix.length; i++) {          // 行
    for (int j = 0; j < matrix[i].length; j++) {   // 列,用 matrix[i].length
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

内层长度用 matrix[i].length 而不是固定数,这样不规则数组也能正确遍历。把 matrix[i] 看成一个普通一维数组,二维遍历其实就是”对每一行,做一遍一维遍历”。

Warning

遍历二维数组别写死内层长度(如 j < 4)。一旦遇上不规则数组或行列数变化,就会越界或漏元素。永远用 matrix[i].length,让每行自己决定宽度。

增强 for 也能遍历二维

for (int[] row : matrix) {          // 每次取出一整行(一维数组)
    for (int val : row) {           // 再遍历这一行
        System.out.print(val + " ");
    }
    System.out.println();
}
```bash

写法更简洁,同样不会越界。"行"是个一维数组,内层增强 for 自己知道到哪结束。

## 三维及以上

理论上还能有三维数组 `int[][][]`,表示"数组的数组的数组",可以想象成"一本书的每一页是一行,每行是一排字"。但实际开发超过二维就很少用,真到了那种复杂度,往往该用对象或集合来表达更清楚了。强行用高维数组,代码会很难读也很难维护。

```java
int[][][] cube = new int[2][3][4];   // 2 层,3 行,4 列

三维数组的遍历就是三层嵌套 for,原理和二维一样,每层用对应维度的 length。

常见错误

  • 忘了给不规则数组的每一行 new,直接访问报空指针。
  • 内层循环写死长度,遇到不规则数组就出错。
  • matrix.length 当成”总元素个数”,其实它只是”行数”。
  • 以为二维数组一定每行等长,其实不是。
Note

二维数组在 Java 里永远是一维数组的数组,没有真正的”矩形内存”。记住这一点,不规则数组、遍历、赋值全部顺理成章。

求二维数组的行数和列数

int rows = matrix.length;           // 行数
int cols = matrix[0].length;        // 列数(假设至少一行)
```java

如果可能没有行,要先判断 `rows > 0` 再取列数,否则越界。

不规则数组求"总元素数"得累加:

```java
int total = 0;
for (int i = 0; i < tri.length; i++) {
    total += tri[i].length;         // 每行长度不同,逐个加
}

Java 25 LTS 与老写法对照

二维数组的语法从 Java 1.0 起就没变过。但老代码里有些写法现在不推荐了:

写法状态说明
int[][] arr✅ 推荐中括号跟类型
int arr[][]⚠️ 不推荐能跑,但不直观
int[] arr[]⚠️ 不推荐更不直观
new int[3][4]✅ 推荐同时指定行列
new int[3][] 再逐行 new✅ 推荐不规则数组专用

Java 21 引入了 Stream API,也能处理二维数组:

import java.util.Arrays;

int[][] matrix = {{1,2},{3,4}};
// 把二维数组打平成一维流
Arrays.stream(matrix).flatMapToInt(Arrays::stream).forEach(System.out::println);
```bash

这是函数式写法,初学了解即可,后面学 Stream 时会展开。

## 二维数组的适用场景

什么时候用二维数组?

- 棋盘、矩阵、迷宫等网格数据
- 像素图片(每个像素一个值)
- 表格数据(行=记录,列=字段)
- 动态规划里的状态表
- 图的邻接矩阵表示

如果数据是"对象"(比如每个格子有多个属性),用对象数组或 List 更合适。二维数组适合"纯数字、结构规整"的场景。

## 二维数组的浅拷贝陷阱

二维数组的拷贝比一维数组复杂。直接 `Arrays.copyOf` 只复制外层引用,内层数组还是共享的:

```java
int[][] original = {{1,2},{3,4}};
int[][] copy = Arrays.copyOf(original, original.length);
copy[0][0] = 99;
System.out.println(original[0][0]);   // 99,被改了!

这叫”浅拷贝”。要”深拷贝”得逐行复制:

int[][] deepCopy = new int[original.length][];
for (int i = 0; i < original.length; i++) {
    deepCopy[i] = Arrays.copyOf(original[i], original[i].length);
}
```bash

27 章会详细讲 `Arrays.copyOf` 的用法。

## 速查表

| 操作 | 写法 | 说明 |
|------|------|------|
| 声明二维 | `int[][] arr;` | 两对中括号 |
| 静态初始化 | `{{1,2},{3,4}}` | 每行用花括号 |
| 动态初始化 | `new int[3][4]` | 同时指定行列 |
| 不规则 | `new int[3][]` | 只指定行,列后补 |
| 取行数 | `arr.length` | 外层数组长度 |
| 取列数 | `arr[0].length` | 第一行的长度 |
| 遍历 | 双层 for | 内层用 arr[i].length |
| 取总元素数 | 逐行累加 | 不规则数组专用 |
| 深拷贝 | 逐行 copyOf | 浅拷贝会共享内层数组 |

## 常见疑问

**Q:二维数组一定要每行等长吗?**
不一定。Java 的二维数组本质是"数组的数组",每行独立,可以不等长。只有 `new int[r][c]` 这种写法才保证等长。

**Q:能定义四维或更高维数组吗?**
语法上能,`int[][][][]` 这样的声明合法。但超过二维可读性急剧下降,工程里基本不用。遇到高维数据优先考虑对象建模。

**Q:如何复制二维数组?**
没有一行搞定的方法。得先复制外层,再逐行 `Arrays.copyOf` 每个内层数组。这叫"深拷贝",第 27 章会展开。

**Q:二维数组和 List<List<Integer>> 哪个好?**
看场景。数组性能更好、内存更紧凑;List 更灵活、API 更丰富。固定大小的数据用数组,频繁增删的用 List。