5. Java 数组核心知识点
一、数组概述
1.1 什么是数组
数组是相同数据类型元素的有序集合,使用一个变量名管理多个同类型数据。本质是一个固定长度的容器。
1.2 核心特点
|
特点 |
说明 |
|
类型统一 |
数组中所有元素必须是同一数据类型(如全部 int 或全部 String) |
|
长度固定 |
数组一旦创建,长度不可修改。 |
|
有序性 |
元素按索引(下标)排列,索引从 0 开始 |
|
引用类型 |
数组是引用数据类型,数组名存储的是堆内存地址而非元素本身 |
1.3 存储逻辑
数组在内存中占据连续的存储空间。以 int[] arr = {10, 20, 30} 为例,堆内存中开辟 3 个连续的 int 空间(每个 4 字节),索引 0 对应 10、索引 1 对应 20、索引 2 对应 30。通过「数组名 + 索引」即可 O(1) 定位任意元素。
二、数组的初始化
2.1 静态初始化
创建数组时直接指定元素值,长度由元素个数自动推断。适用于已明确所有元素值的场景。
三种写法:
// 写法1:简化版(推荐)
数据类型[] 数组名 = {元素1, 元素2, 元素3, ...};
// 写法2:完整版
数据类型[] 数组名 = new 数据类型[]{元素1, 元素2, 元素3, ...};
// 写法3:声明与初始化分离
数据类型[] 数组名;
数组名 = new 数据类型[]{元素1, 元素2, ...};
示例:
int[] intArr = {10, 20, 30, 40};
String[] strArr = {"Java", "数组", "静态初始化"};
boolean[] boolArr = {true, false, true};
注意事项:
|
规则 |
说明 |
|
禁止同时指定长度 |
|
|
元素必须有默认值 |
静态初始化时元素已显式赋值,不存在默认值概念;默认值仅出现在动态初始化中 |
|
写法3 不推荐 |
声明与初始化分离容易忘记赋值导致空指针 |
2.2 动态初始化
先指定数组长度,元素后续赋值。未赋值的元素使用对应类型的默认值。
语法:
数据类型[] 数组名 = new 数据类型[长度];
示例:
int[] arr = new int[3]; // 长度为 3,元素默认值均为 0
arr[0] = 10;
arr[1] = 20;
// arr[2] 未赋值,仍为默认值 0
各类型默认值:
|
数据类型 |
默认值 |
|
byte / short / int / long |
0 |
|
float / double |
0.0 |
|
boolean |
false |
|
char |
'\u0000'(空字符) |
|
引用类型(String、自定义类等) |
null |
2.3 静态 vs 动态初始化
|
维度 |
静态初始化 |
动态初始化 |
|
语法 |
|
|
|
元素来源 |
创建时直接指定 |
先定长度,后逐个赋值 |
|
适用场景 |
已知所有元素值 |
元素值待计算或批量填充 |
|
默认值 |
无(已显式赋值) |
有(各类型默认值) |
三、数组的访问与遍历
3.1 通过索引访问元素
int[] arr = {10, 20, 30};
// 读取
int first = arr[0]; // 10
// 修改
arr[1] = 200; // 索引 1 改为 200
索引合法范围:0 ~ arr.length - 1。超出范围抛出 ArrayIndexOutOfBoundsException。
3.2 两种遍历方式
int[] arr = {10, 20, 30, 40};
// 方式1:普通 for 循环(可获取索引、可修改元素)
for (int i = 0; i < arr.length; i++) {
System.out.println("索引" + i + ": " + arr[i]);
}
// 方式2:增强 for 循环(foreach,JDK 1.5+)
for (int num : arr) {
System.out.println(num);
}
|
维度 |
普通 for |
增强 for |
|
可获取索引 |
是 |
否 |
|
可修改元素 |
是(通过索引赋值) |
否(num 是副本) |
|
代码简洁度 |
一般 |
高 |
|
适用场景 |
需要索引或修改元素的场景 |
仅遍历读取 |
3.3 经典练习题
求和:
int[] arr = {1, 2, 3, 4, 5};
int sum = 0;
for (int num : arr) {
sum += num;
}
System.out.println(sum); // 15
查找最大值:
int[] arr = {12, 45, 9, 78, 33};
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
System.out.println(max); // 78
反转(双指针法):
int[] arr = {1, 2, 3, 4, 5};
int left = 0, right = arr.length - 1;
while (left < right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
// 结果: {5, 4, 3, 2, 1}
四、数组的内存原理
理解数组在 JVM 内存中的存储方式,是排查空指针和引用传递问题的前提。
4.1 JVM 内存分区(简化)
|
内存区域 |
存储内容 |
特点 |
|
虚拟机栈(Stack) |
局部变量、方法调用栈帧、数组名(引用) |
线程私有,方法结束即销毁 |
|
堆(Heap) |
数组对象、类的实例对象 |
线程共享,由 GC 回收 |
|
方法区(Method Area) |
类的元数据、静态变量、常量池 |
线程共享,JDK 8 后称元空间 |
数组涉及的核心区域:栈(存引用)+ 堆(存实体)。
4.2 逐行拆解
以下代码的内存变化:
int a = 10;
int[] arr;
arr = new int[3];
arr[0] = 20;
arr[1] = 30;
arr = new int[2];
|
阶段 |
栈(main 栈帧) |
堆 |
|
|
a = 10, arr = null |
— |
|
|
a = 10, arr = 0x0011 |
地址 0x0011: {length:3, [0]=0, [1]=0, [2]=0} |
|
|
a = 10, arr = 0x0011 |
地址 0x0011: {length:3, [0]=20, [1]=30, [2]=0} |
|
|
a = 10, arr = 0x4455 |
地址 0x4455: {length:2, [0]=0, [1]=0};地址 0x0011 成为垃圾对象,等待 GC |
关键结论:arr = new int[2] 将引用指向新数组后,旧数组失去引用成为垃圾对象。
4.3 内存图示意
栈区(Stack) 堆区(Heap)
┌─────────────┐ ┌──────────────────┐
│ a = 10 │ │ 地址 0x4455 │
│ arr = 0x4455│────────→ │ int[] length=2 │
└─────────────┘ │ [0]=0 [1]=0 │
└──────────────────┘
┌──────────────────┐
│ 地址 0x0011 (垃圾) │
│ int[] length=3 │
│ [0]=20 [1]=30 [2]=0│
└──────────────────┘
4.4 核心知识点
- 数组名是栈中的引用变量,存储堆地址,而非数组本身
- 所有数组元素(无论基本类型还是引用类型)都存储在堆中
- 数组创建后未赋值时,元素使用各类型默认值
- 引用指向新地址后,原数组若无其他引用,将被 GC 回收
- 基本类型变量(如
int a)的值直接存在栈中;数组引用变量存的是堆地址
五、常见异常与避坑指南
5.1 ArrayIndexOutOfBoundsException(索引越界)
原因:访问的索引不在 0 ~ length-1 范围内。
int[] arr = {1, 2};
System.out.println(arr[2]); // 异常:长度为 2,最大索引为 1
避免方式:遍历时始终使用 i < arr.length 作为循环条件。
5.2 NullPointerException(空指针)
原因:数组引用为 null,却尝试访问元素。
int[] arr = null;
System.out.println(arr[0]); // 异常:arr 未指向任何堆内存空间
避免方式:确保数组已完成初始化(new 或静态赋值)后再访问。
5.3 引用传递的副作用
多个引用指向同一数组时,通过任一引用修改元素都会影响所有引用:
int[] arr1 = {1, 2};
int[] arr2 = arr1; // arr2 和 arr1 指向同一数组
arr2[0] = 100;
System.out.println(arr1[0]); // 输出 100
需要独立副本时,应使用 clone() 或 System.arraycopy()。
5.4 数组地址值
直接打印数组名会输出地址标识:
int[] arr = {10, 20, 30};
System.out.println(arr); // 输出如 [I@7852e922
// [ 表示数组,I 表示 int 类型,@ 后为哈希值
六、面试高频考点
Q1:int[] arr = new int[3]; System.out.println(arr.length); 输出什么?
输出 3。length 是数组对象的属性,存储在堆中,通过引用读取。
Q2:int[] arr = null; arr[0] = 10; 会怎样?
抛出 NullPointerException。arr 为 null,没有指向堆中的数组,无法访问元素。
Q3:基本类型变量和数组引用变量的区别?
基本类型变量(如 int a = 10)的值直接存储在栈中;数组引用变量(如 int[] arr)的值是堆地址,真正的数组数据在堆中。
Q4:静态初始化能同时指定长度吗?
不能。new int[3]{1, 2, 3} 会编译错误。静态初始化的长度由元素个数自动计算。
Q5:int[] a = {10}; int[] b = a; b[0] = 20; 后 a[0] 是多少?
20。a 和 b 指向同一个堆数组,修改 b[0] 等价于修改同一块内存。
总结速查
|
主题 |
核心要点 |
|
数组本质 |
相同类型、固定长度、有序集合、引用类型 |
|
静态初始化 |
|
|
动态初始化 |
|
|
元素访问 |
|
|
遍历 |
普通 for(灵活) / 增强 for(简洁) |
|
内存模型 |
栈存引用地址,堆存数组实体 |
|
常见异常 |
索引越界、空指针、引用传递副作用 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)