今日
- 数组本质
数组是连续内存的容器,存放一组相同类型数据;属于引用数据类型,无论存基本类型还是包装类,数组对象本身永远在堆,栈只存地址。 - 内存两大分区分工
栈内存(Stack)
存放局部变量:数组引用变量、方法形参、方法内普通变量;
方法执行完毕,栈帧直接销毁,引用变量消失;
只存内存地址,不存数组真实元素。
堆内存(Heap)
new 出来的数组实体全部在堆;内存地址连续;
数组有固定长度,创建后长度不可修改;
堆对象没有引用指向时,等待 GC 垃圾回收。 - 三种数组创建方式内存图解
方式 1:动态初始化(指定长度,默认赋值)
int[] arr = new int[5];
栈:创建变量 arr;
堆开辟连续 5 块 int 空间,分配堆内存地址;
栈 arr 保存堆地址,指向堆数组;
默认初始化规则:
基本类型:int=0、double=0.0、boolean=false、char='\u0000'
引用类型数组(String []、自定义类 []):所有元素默认null
方式 2:静态初始化(直接赋值,长度自动计算)
int[] arr = {10,20,30};
int[] arr2 = new int[]{1,2,3};
堆直接开辟对应长度内存,存入给定值;栈保存地址。
方式 3:先声明、后赋值
int[] arr; //栈只有变量,无指向
arr = new int[3]; //栈arr拿到堆地址
只声明没 new:数组引用是空,不能使用。 - 数组赋值的内存机制(重中之重)
int[] a = new int[2];
int[] b = a;
a 在栈存堆地址;b=a是地址拷贝,不是新建数组;
a、b 两个引用指向堆中同一个数组;
b 修改数组元素,a 读取也会变化,共用堆数据。 - 基本类型数组 vs 引用类型数组内存
int[]:堆里存实实在在 int 数值;
String[] / Person[]:堆数组里存的是对象地址,数组每个格子都是引用;
Person[] pArr = new Person[2];
pArr[0] = new Person();
数组在堆,数组元素存 Person 对象的堆地址。 - 数组参数传递(值传递)
Java 只有值传递:
数组做方法入参,传递的是堆地址副本;
方法内修改数组元素,原数组变化(共用堆);
方法内给形参整体 new 新数组,只是修改副本地址,外部原数组完全不受影响。 - 多维数组内存(二维数组)
int[][] arr = new int[3][4]
堆第一层:长度为 3 的一维数组,每个元素存子数组地址;
第二层:3 个长度为 4 的小数组;
不规则二维数组:int[][] arr = new int[3][],只创建外层,内层需要手动 new,默认内层元素为 null。 - 数组长度不可变
数组创建时就固定占用连续堆内存,不能加长缩短;想要变长只能新建更大数组,复制元素。
明日
Java数组在方法中传递
问题及解决方法 - 数组下标越界:ArrayIndexOutOfBoundsException
现象:运行报错,索引超出范围
原因:数组长度 length,合法下标0 ~ length-1;下标 < 0 或 ≥数组长度;循环 i 终止条件写错(i<=length)。
解决:
严格用i < arr.length做循环条件;
打印 length 核对数组真实长度,排查手写死下标;
遍历一律用 fori / 增强 for,不要硬写数字下标。 - 空指针异常 NullPointerException(数组最高频)
场景 1:数组引用为 null,调用 length、访问下标
int[] arr = null;
System.out.println(arr.length); //空指针
原因:栈引用没有指向堆任何数组。
解决:使用前先判空if(arr != null);保证数组被 new 初始化。
场景 2:引用类型数组元素是 null,调用元素属性 / 方法
String[] strs = new String[2];
strs[0].length(); //空指针,strs[0]默认null
原因:数组存在,但下标位置没有存对象,是空地址。
解决:先判断strs[0] != null再调用方法。
场景 3:二维数组外层不为 null,内层子数组为 null
int[][] arr = new int[3][];
arr[0][1] = 10; //空指针
解决:手动初始化内层:arr[0] = new int[2]; - 数组声明语法报错
错误写法:int[5] arr;
原因:长度只能写在 new 后面,声明不能带长度。
正确:int[] arr = new int[5];
