数组是Java中最基础也是最常用的数据结构之一。简单来说,数组就是一组相同类型数据的集合,这些数据在内存中连续存储,通过索引(下标)来访问每个元素。我第一次接触数组时,最直观的感受就是它像一排整齐排列的储物柜,每个柜子都有编号(索引),里面存放着物品(数据)。
数组的核心特性包括:
在实际开发中,数组特别适合处理需要快速随机访问的场景。比如我们开发一个学生成绩管理系统,用数组来存储全班50个学生的数学成绩就非常合适,因为可以快速通过学生编号(索引)查找对应的成绩。
Java中声明数组有两种基本语法格式:
java复制// 方式一:数据类型[] 数组名;
int[] scores;
// 方式二:数据类型 数组名[];
double heights[];
从编码规范角度,我强烈推荐使用第一种方式。因为这种方式更清晰地表达了"int数组"这个类型概念,而且与Java的其他语法更一致。第二种方式是从C语言继承过来的,容易造成混淆。
数组初始化分为静态初始化和动态初始化两种方式。
静态初始化是在声明数组的同时直接指定元素值:
java复制// 完整格式
int[] arr1 = new int[]{90, 85, 78, 92};
// 简化格式(只能在声明时使用)
int[] arr2 = {90, 85, 78, 92};
动态初始化是只指定数组长度,由系统分配默认值:
java复制int[] arr3 = new int[4]; // 默认值:0,0,0,0
String[] names = new String[3]; // 默认值:null,null,null
注意:静态初始化的简化格式不能拆分使用。以下写法是错误的:
java复制int[] arr; arr = {1,2,3}; // 编译错误
理解数组的内存模型对掌握Java编程非常重要。当我们创建一个数组时:
java复制int[] arr = new int[3];
JVM会在堆内存中分配一块连续的空间,用于存储数组元素。同时,在栈内存中会创建一个引用变量arr,它保存的是堆内存中数组对象的地址。

数组元素通过索引访问,索引从0开始。例如:
java复制arr[0] = 90; // 第一个元素
arr[1] = 85; // 第二个元素
arr[2] = 78; // 第三个元素
访问数组元素时最常见的错误就是数组越界(ArrayIndexOutOfBoundsException)。比如:
java复制System.out.println(arr[3]); // 错误!最大索引是2
我在实际开发中总结出一个技巧:当需要遍历数组时,最好使用数组的length属性而不是硬编码长度:
java复制for(int i=0; i<arr.length; i++) {
System.out.println(arr[i]);
}
二维数组可以理解为"数组的数组",最常见的应用场景就是表格数据。比如一个班级的成绩表,有3个学生,每个学生有5门课的成绩:
java复制int[][] scores = new int[3][5];
二维数组的静态初始化:
java复制int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Java支持不规则数组(每行长度不同):
java复制int[][] triangle = new int[3][];
triangle[0] = new int[1];
triangle[1] = new int[2];
triangle[2] = new int[3];
这种结构在某些特殊场景下很有用,比如存储三角形的数字图案。但要注意访问时要额外检查每行的长度,避免越界。
除了基本的for循环,Java还提供了几种遍历数组的方式:
java复制for(int score : scores) {
System.out.println(score);
}
java复制System.out.println(Arrays.toString(arr));
java复制Arrays.stream(arr).forEach(System.out::println);
Java标准库提供了方便的数组操作方法:
java复制// 排序
int[] numbers = {3,1,4,2};
Arrays.sort(numbers); // [1,2,3,4]
// 二分查找(数组必须已排序)
int index = Arrays.binarySearch(numbers, 3); // 返回2
注意:如果数组未排序就调用binarySearch,结果是不确定的。
数组复制有多种方式,各有优缺点:
| 方法 | 示例 | 特点 |
|---|---|---|
| System.arraycopy | System.arraycopy(src,0,dest,0,src.length) |
高效,但需要预先分配目标数组 |
| Arrays.copyOf | int[] copy = Arrays.copyOf(original, newLength) |
简洁,会自动创建新数组 |
| clone()方法 | int[] copy = original.clone() |
最简洁,但很少使用 |
我在项目中通常使用Arrays.copyOf,因为它在大多数情况下都能满足需求,而且代码更简洁。
空指针异常:未初始化的数组引用是null
java复制int[] arr;
System.out.println(arr[0]); // NullPointerException
数组越界:访问不存在的索引
java复制int[] arr = new int[3];
System.out.println(arr[3]); // ArrayIndexOutOfBoundsException
类型不匹配:尝试存储错误类型的数据
java复制String[] names = new String[3];
names[0] = 123; // 编译错误
预分配足够空间:如果知道数组大致需要的容量,应该一次性分配足够空间,避免频繁扩容(虽然数组本身不能扩容,但这种考虑适用于使用数组实现的集合类)。
批量操作优于单元素操作:使用System.arraycopy进行批量复制比循环复制单个元素效率高得多。
考虑缓存友好性:由于数组在内存中是连续存储的,顺序访问比随机访问更快,这被称为"空间局部性"原理。
基本类型优先:对于大量数据,使用基本类型数组(如int[])比包装类型数组(如Integer[])更节省内存,访问更快。
在简单的2D游戏开发中,二维数组常用来表示游戏地图。例如:
java复制// 0表示空地,1表示墙,2表示宝物
int[][] map = {
{1,1,1,1,1},
{1,0,0,2,1},
{1,0,1,0,1},
{1,2,0,0,1},
{1,1,1,1,1}
};
这种表示方法简单直观,便于进行碰撞检测、寻路算法等操作。
在图像处理中,图像可以表示为一个三维数组(对于RGB图像):
java复制int[][][] image = new int[height][width][3]; // 3表示RGB三个通道
通过操作这个数组,我们可以实现各种图像处理效果,如滤镜、旋转、缩放等。
数组是算法题中最常用的数据结构之一。例如经典的"两数之和"问题:
java复制public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
掌握数组的各种操作技巧对解决算法问题至关重要。
虽然数组非常基础且高效,但它也有一些明显的局限性:
固定长度:创建后无法改变大小,这在很多动态场景中很不方便。
缺乏高级操作:需要手动实现搜索、排序等常见操作。
类型单一:所有元素必须是相同类型。
因此在现代Java开发中,我们更多使用集合框架(如ArrayList)来处理动态数据。但理解数组的原理和特性仍然是必要的,因为:
我在实际项目中通常遵循这样的原则:在性能关键路径或需要与底层API交互时使用数组,其他情况下优先使用集合类。