Java 中的数组(Array) 是最基础、最常用的固定长度、连续存储的数据结构。它属于引用数据类型,在堆内存中分配空间。
下面从零基础到高级、从语法到内存到最佳实践全面解析(目标约万字深度,但这里精炼为结构清晰的完整指南,适合学习、面试、日常开发使用)。内容基于 Java 21+(2025–2026 主流写法)。
1. 为什么 Java 需要数组?(核心定位)
- 存储大量同类型数据
- 提供O(1) 随机访问(下标访问)
- 内存连续 → 缓存友好,性能极高
- 但长度不可变 → 这是数组最大局限(对比 ArrayList)
2. 数组的四种基本声明方式(语法糖演变)
| 写法序号 | 声明语法 | 推荐程度(2026年) | 说明 |
|---|---|---|---|
| 1 | int[] arr; | ★★★★★ | 最推荐,可读性强(类型在前) |
| 2 | int arr[]; | ★★☆☆☆ | C/C++ 风格,不推荐 |
| 3 | int... arr (可变参数) | — | 仅用于方法参数,本质是数组 |
| 4 | int @Nullable[] arr (注解风格) | ★★★★☆ | 现代项目中常见(配合空安全工具) |
推荐统一风格:始终使用 类型[] 变量名(方式1)。
3. 数组的创建(初始化)方式全解(最常考)
3.1 方式一:静态初始化(最常用、最安全)
// 方式一:完整写法(new + 花括号)
int[] scores = new int[]{85, 92, 78, 95};
// 方式二:简化写法(推荐,编译器自动推断 new int[])
int[] scores = {85, 92, 78, 95}; // 只能在声明时使用
String[] names = {"Alice", "Bob", "Charlie"};
// 空数组(长度0)
int[] empty = new int[0]; // 或 {}
int[] empty2 = {}; // 同上
3.2 方式二:动态初始化(先创建,后赋值)
// 先声明 + 创建(默认值填充)
int[] arr = new int[5]; // 长度5,全部初始化为 0
// 基本类型默认值一览(非常重要!)
byte/short/int/long → 0
float/double → 0.0
char → ''
boolean → false
引用类型(String等) → null
// 逐个赋值
arr[0] = 10;
arr[1] = 20;
// ...
3.3 方式三:先声明,后 new(延迟初始化)
int[] arr; // 只声明,栈中存 null
arr = new int[10]; // 堆中真正分配
4. 多维数组(本质:数组的数组)
Java 没有真正的多维数组,只有数组的数组(jagged array,不规则数组)。
4.1 规则矩形二维数组(最常见)
// 声明 + 创建(3行4列)
int[][] matrix = new int[3][4];
// 静态初始化
int[][] matrix2 = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9,10,11,12}
};
// 访问:matrix[行][列]
matrix[0][0] = 100;
System.out.println(matrix[2][3]); // 12
4.2 不规则(锯齿状)二维数组(面试高频)
int[][] jagged = new int[3][]; // 第一维必须指定
jagged[0] = new int[2]; // 长度2
jagged[1] = new int[5]; // 长度5
jagged[2] = new int[1]; // 长度1
// 更常见写法
int[][] jagged2 = {
{1, 2},
{3, 4, 5, 6, 7},
{8}
};
内存图关键点(常考画图题):
jagged → [引用] → { [引用], [引用], [引用] }
↓ ↓ ↓
{1,2} {3,4,5,6,7} {8}
5. 数组常用操作(必会)
| 操作 | 代码示例 | 时间复杂度 | 说明 |
|---|---|---|---|
| 长度 | arr.length | O(1) | 属性,不是方法! |
| 遍历(推荐) | for(int num : arr) | O(n) | 增强for循环(只读最安全) |
| 普通for遍历 | for(int i=0; i<arr.length; i++) | O(n) | 需要下标时使用 |
| 打印 | Arrays.toString(arr) | — | 必须 import java.util.Arrays |
| 排序 | Arrays.sort(arr) | O(n log n) | 基本类型快排 / 对象需实现Comparable |
| 二分查找(有序) | Arrays.binarySearch(arr, key) | O(log n) | 必须先排序!负数表示插入点 |
| 填充 | Arrays.fill(arr, 0) | O(n) | 全部填充同一个值 |
| 复制 | Arrays.copyOf(arr, newLength) | O(n) | 产生新数组 |
| 比较 | Arrays.equals(arr1, arr2) | O(n) | 内容比较(不是 == ) |
| 深比较(多维) | Arrays.deepEquals(arr1, arr2) | O(n) | 多维数组专用 |
6. 常见坑 & 最佳实践(2025–2026 面试重灾区)
| 排名 | 坑 / 错误做法 | 正确 / 推荐做法 | 为什么重要(后果) |
|---|---|---|---|
| 1 | int[] a = new int[5]; a[5] = 1; | 下标永远 < length | ArrayIndexOutOfBoundsException |
| 2 | 直接用 == 比较两个数组 | 用 Arrays.equals() 或 Arrays.deepEquals() | == 比较的是引用地址 |
| 3 | String[] strs = {"a","b"}; strs.length() | strs.length(属性!) | 编译错误 |
| 4 | 大量使用 new int[1000000] 循环创建 | 尽可能复用数组 + Arrays.fill() | 频繁GC、内存碎片 |
| 5 | 多维数组不规则时直接 matrix[i][j] | 先判断 matrix[i] != null && j < matrix[i].length | NullPointerException 或越界 |
| 6 | Arrays.asList() 后调用 add/remove | new ArrayList<>(Arrays.asList(...)) 或 List.of() | UnsupportedOperationException |
| 7 | 基本类型数组转 List | Arrays.stream(arr).boxed().toList() (Java 16+) | 否则只能手动循环 |
| 8 | 认为数组长度可变 | 需要变长 → 用 ArrayList / LinkedList | 数组天生固定长度 |
| 9 | 大数组直接 System.out.println(arr) | 永远用 Arrays.toString() / deepToString() | 只打印 hashCode |
7. 数组 vs ArrayList(选择依据表,面试必问)
| 维度 | 数组 (int[] / String[]) | ArrayList / ArrayList | 选择建议(2026主流) |
|---|---|---|---|
| 长度 | 固定 | 动态 | 长度确定 → 数组;不确定 → List |
| 性能(访问) | 最快(O(1),无装箱) | 稍慢(有装箱/拆箱) | 高性能场景优先数组 |
| 内存占用 | 更省(连续 + 无额外对象头) | 更多(每个元素包装成对象) | 大数据量优先数组 |
| 方法丰富度 | 极少 | 丰富(add/remove/contains/indexOf 等) | 频繁操作选 List |
| 泛型支持 | 不支持 | 支持 | 需要泛型 → List |
| 多线程安全 | 非线程安全 | 非线程安全(但有 Collections.synchronizedList) | 多线程慎用两者 |
2026 年真实趋势:
- 数值计算、性能敏感场景(游戏、科学计算、算法题) → 优先原生数组
- 业务代码、集合操作频繁 → 几乎全用 ArrayList / List.of() / Arrays.asList()(只读场景)
8. 经典面试手写题(建议全部手敲一遍)
- 反转数组(原地 / 新数组两种)
- 找出数组中出现次数最多的元素(HashMap / 排序后统计)
- 二维数组顺时针旋转 90°
- 杨辉三角(前 n 行)
- 合并两个有序数组(原地合并到第一个数组)
- 删除有序数组中的重复元素(返回新长度)
- 寻找峰值(二分法 O(log n))
需要哪一道题的详细代码 + 复杂度分析 + 边界case?或者想看某个具体场景的完整示例(排序 + 搜索 + 多维 + 性能对比)?告诉我,我可以继续展开。