数组是Java中一种非常重要的数据结构,用于存储固定大小的同类型元素。掌握数组是学习Java的基础。下面从定义、声明、创建、初始化、操作、内存模型等方面进行详细说明。
一、数组的定义
数组是一个容器对象,它能够在内存中连续存储固定数量的相同类型的数据。
-
特点:
-
长度一旦确定,不可改变。
-
所有元素必须是同一数据类型(可以是基本类型或引用类型)。
-
通过索引访问元素,索引从 0 开始。
-
数组本身是对象,存储在堆内存中。
-
二、数组的声明
声明数组只是定义了一个引用变量,并不创建数组对象。
// 推荐方式(类型[] 数组名)
int[] arr1; // 声明一个int数组
String[] arr2; // 声明一个String数组
// 另一种方式(类型 数组名[]),为了与C/C++兼容,不推荐
int arr3[];
-
声明时不能指定数组长度(如 int[5] arr; 是错误的)。
-
数组变量存储的是指向数组对象的引用(地址)。
三、数组的创建
使用 new 关键字分配内存空间,并指定长度。
int[] arr = new int[5]; // 创建长度为5的int数组,每个元素默认值为0
String[] names = new String[3]; // 创建长度为3的String数组,默认值为null
-
长度可以是变量或常量,但必须是非负整数。
-
数组创建后,每个元素会自动赋予默认值:
-
整数类型(byte, short, int, long):0
-
浮点类型(float, double):0.0
-
字符类型(char):'\\u0000'(空字符)
-
布尔类型(boolean):false
-
引用类型:null
-
四、数组的初始化
为数组元素赋初始值。有两种方式:
4.1 静态初始化
在创建数组的同时直接指定元素值,由编译器推断长度。
int[] arr = {1, 2, 3, 4, 5}; // 简化语法
String[] names = new String[]{"Alice", "Bob", "Charlie"}; // 完整语法
4.2 动态初始化
先创建数组(指定长度),然后通过循环或其他方式逐一赋值。
int[] arr = new int[3];
for (int i = 0; i < arr.length; i++) {
arr[i] = i * 10;
}
五、数组的访问
通过索引访问元素,索引范围:0 到 length-1。
int[] arr = {10, 20, 30};
System.out.println(arr[0]); // 输出10
arr[1] = 25; // 修改第二个元素为25
-
每个数组都有一个 length 属性,表示数组的长度(元素个数)。
-
访问越界会抛出 ArrayIndexOutOfBoundsException。
六、数组的遍历
6.1 普通for循环
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
6.2 增强for循环(for-each)
for (int num : arr) {
System.out.println(num);
}
-
增强for循环不能修改数组元素(不能通过迭代变量赋值),也无法获取索引。
6.3 Java 8 Stream方式
import java.util.Arrays;
Arrays.stream(arr).forEach(System.out::println);
确保arr是一个已初始化的数组变量。例如:
int[] arr = {1, 2, 3, 4, 5};
七、数组的内存结构
-
数组变量存储在栈内存中,存储的是数组对象的引用(地址)。
-
数组对象本身存储在堆内存中,包含:类型信息、长度标识、以及连续的元素数据。
-
基本类型数组直接存储值;引用类型数组存储的是对象的引用(地址)。
int[] a = new int[3]; // a指向堆中的数组对象
八、多维数组
Java中多维数组本质上是“数组的数组”,可以是不规则的(每个子数组长度可以不同)。
8.1 声明和创建
int[][] matrix = new int[3][4]; // 3行4列的二维数组(每个子数组长度4)
int[][] triangle = new int[3][]; // 只指定行数,列数未定
triangle[0] = new int[1]; // 每一行可以单独创建不同长度的数组
triangle[1] = new int[2];
triangle[2] = new int[3];
8.2 静态初始化及遍历
int[][] arr = {
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
};
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
1 2 3
4 5
6 7 8 9
8.3 访问
int value = arr[1][0]; // 获取第二行第一列的元素(值为4)
arr[2][2] = 10;
代码说明
- int value = arr[1][0]; 表示从二维数组 arr 中获取第二行第一列的元素,并将其赋值给变量 value。
- arr[2][2] = 10; 表示将二维数组 arr 的第三行第三列的元素赋值为 10。
九、数组的常用操作(借助 java.util.Arrays 类)
Arrays 类提供了大量静态方法简化数组操作。
9.1 复制数组
-
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length):高效复制(native方法)。
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
System.arraycopy(src, 1, dest, 2, 3); // 从src索引1开始,复制3个元素,放到dest索引2开始
// dest: [0, 0, 2, 3, 4]
Arrays.copyOf(original, newLength):返回一个新数组,包含原数组的前newLength个元素(或填充默认值)。
int[] newArr = Arrays.copyOf(src, 3); // [1, 2, 3]
int[] expanded = Arrays.copyOf(src, 10); // [1,2,3,4,5,0,0,0,0,0]
-
Arrays.copyOfRange(original, from, to):复制指定范围。
-
数组的 clone() 方法:int[] copy = src.clone(); 返回一个完整副本(浅拷贝)。
9.2 排序
int[] nums = {3, 1, 4, 1, 5, 9};
Arrays.sort(nums); // 对整个数组排序
Arrays.sort(nums, 1, 4); // 对索引[1,4)范围内的元素排序
-
对对象数组排序,要求元素实现了 Comparable 接口,或者提供 Comparator。
9.3 二分查找(数组必须有序)
int index = Arrays.binarySearch(nums, 4); // 返回4的索引,如果不存在返回负插入点
9.4 填充
int[] arr = new int[5];
Arrays.fill(arr, 10); // 所有元素设为10
Arrays.fill(arr, 1, 3, 20); // 索引[1,3)设为20
9.5 比较
-
Arrays.equals(arr1, arr2):比较两个一维数组是否相等(元素个数和对应元素相等)。
-
Arrays.deepEquals(arr1, arr2):比较多维数组
9.6 转换为字符串
int[] arr = {1, 2, 3};
System.out.println(Arrays.toString(arr)); // 输出 "[1, 2, 3]"
int[][] matrix = {{1,2},{3,4}};
System.out.println(Arrays.deepToString(matrix)); // 输出 "[[1, 2], [3, 4]]"
9.7 数组转List
String[] strArr = {"A", "B", "C"};
List<String> list = Arrays.asList(strArr);
十、数组的局限性
-
长度固定:一旦创建无法扩容,需要扩容时必须新建数组并复制。
-
类型固定:只能存储同一类型(或其子类型,通过多态可以存子类对象,但声明类型为父类)。
-
添加/删除不便:没有直接的方法,需要手动移动元素。
-
缺乏丰富的方法:相比集合框架(如 ArrayList),数组功能有限。
十一、数组与泛型
Java中不能直接创建泛型数组(如 T[] arr = new T[10]; 会编译错误),因为泛型在运行时类型会被擦除。但可以通过 (T[]) new Object[size] 强转,或者使用 ArrayList<T> 替代。
// 泛型数组的正确创建方式(通过反射)
public static <T> T[] newArray(Class<T> type, int length) {
return (T[]) Array.newInstance(type, length);
}
十二、数组的性能
-
优点:随机访问快(O(1)),内存连续,对CPU缓存友好。
-
缺点:插入和删除元素慢(需要移动大量元素),长度固定。
十三、常见错误
空指针异常:数组变量未初始化(为 null)就访问元素。
int[] arr = null;
System.out.println(arr[0]); // NullPointerException
-
索引越界异常:索引超出 [0, length-1] 范围。
十四、最佳实践
-
除非有明确的性能要求(如大量数据、对性能极致追求)或需要与遗留API交互,否则优先使用 ArrayList 等集合类,它们提供了动态扩容和丰富的方法。
-
在方法间传递数组时,注意数组是引用传递,方法内部修改会影响原数组。
-
避免在循环中频繁使用 System.arraycopy 手动扩容,考虑使用 ArrayList。
-
使用 Arrays 工具类简化操作。
总结
Java数组是基础且重要的数据结构,理解其声明、创建、初始化、访问和常用操作是掌握Java编程的基石。虽然集合框架提供了更多便利,但数组在性能要求和底层实现中仍然不可或缺。希望通过本指南,你能全面掌握Java数组的知识点。
