二维数组本质是“数组的数组”,即外层数组存储一维数组引用,各子数组独立分配内存、长度可不等;静态初始化一次性创建所有子数组,规则动态初始化自动创建等长子数组,非规则动态初始化需手动为每行赋值,未初始化行访问会抛nullpointerexception。

二维数组本质是“数组的数组”
Java中没有真正意义上的多维数组,所谓二维数组,其实是一维数组对象组成的数组。外层数组(如 int[][] arr)本身是一个引用类型,它在堆中存储的是若干个一维数组的引用地址,而非连续的二维数据块。每个一维数组又各自在堆中独立分配内存空间,彼此之间不要求长度一致——这就是“不规则二维数组”的底层依据。
三种初始化方式对应的内存行为差异
不同初始化写法直接影响堆中对象的创建时机和结构:
-
静态初始化:
int[][] arr = {{1,2},{3,4,5}};→ 编译期确定所有子数组内容,一次性在堆中创建全部一维数组对象,并由外层数组引用它们;arr[0]和arr[1]指向两个独立、长度不同的int[]对象。 -
规则动态初始化:
int[][] arr = new int[2][3];→ 外层数组被分配固定长度(2),每个元素(arr[0],arr[1])被自动初始化为长度为3的int[]对象(默认值全0),所有子数组连续分配且长度统一。 -
非规则动态初始化:
int[][] arr = new int[3][];→ 仅在外层分配长度为3的引用数组,每个元素初始为null;后续需显式赋值:arr[0] = new int[2]; arr[1] = new int[5];——此时各子数组内存地址互不相关,长度自由。
null 引用与越界访问的风险点
当使用 new int[3][] 这类非规则初始化时,未显式初始化的子数组位置(如 arr[0])值为 null。直接访问 arr[0][0] 会触发 NullPointerException,而非 ArrayIndexOutOfBoundsException。这是因为 JVM 先检查外层数组索引是否越界(0 ≤ i 成立),再解引用 <code>arr[0] ——而该引用为空,故抛出空指针异常。这一点常被误判为下标错误,实则是引用未就绪。
数组长度属性与内存布局的对应关系
每个数组对象都有 length 属性,但含义不同:
-
arr.length返回外层数组的元素个数(即一维数组引用的个数); -
arr[i].length返回第i个一维数组的实际长度(前提是arr[i] != null); - 外层数组的
length在创建后不可变;子数组一旦创建,其length同样不可变,哪怕内容重置或重新赋值给同一索引位(如arr[0] = new int[10]后再arr[0] = new int[5],只是改变了引用指向,原数组仍存在)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











