你真的了解ArrayList 与 Array(数组)的区别吗?
引言
在Java编程中,数据存储和操作是最基础也是最重要的部分。Java提供了两种主要的方式来存储一组数据:传统的数组(Array)和ArrayList。虽然它们都用于存储元素集合,但在实现方式、功能和性能上有着显著差异。本文将深入探讨这两者的区别,并通过JDK 1.8中的代码示例进行详细说明。
1. 基本概念
数组(Array)
数组是Java中最基本的数据结构之一,它是一个固定长度的容器,可以存储相同类型的元素。
// 数组声明和初始化
int[] intArray = new int[5]; // 固定长度为5的整型数组
String[] stringArray = {"Java", "Python", "C++"}; // 初始化时指定元素
ArrayList
ArrayList是Java集合框架的一部分,它实现了List接口,基于动态数组实现,可以根据需要自动调整容量。
// ArrayList声明和初始化
ArrayList
ArrayList
2. 核心区别
2.1 大小固定性
数组的大小在创建时就固定了,无法动态改变。
int[] fixedArray = new int[3];
fixedArray[0] = 1;
fixedArray[1] = 2;
fixedArray[2] = 3;
// fixedArray[3] = 4; // 抛出ArrayIndexOutOfBoundsException
ArrayList的大小是动态的,会根据需要自动增长。
ArrayList
dynamicList.add(1);
dynamicList.add(2);
dynamicList.add(3);
dynamicList.add(4); // 自动扩容,不会抛出异常
System.out.println(dynamicList.size()); // 输出4
2.2 类型安全
数组在运行时检查类型安全。
Object[] objectArray = new String[3];
objectArray[0] = "Hello";
// objectArray[1] = 123; // 抛出ArrayStoreException
ArrayList使用泛型在编译时提供类型安全。
ArrayList
stringList.add("Hello");
// stringList.add(123); // 编译时错误
2.3 性能比较
基本操作性能对比:
// 数组访问
long start = System.nanoTime();
int arrayElement = fixedArray[1];
long end = System.nanoTime();
System.out.println("数组访问时间: " + (end - start) + " ns");
// ArrayList访问
start = System.nanoTime();
int listElement = dynamicList.get(1);
end = System.nanoTime();
System.out.println("ArrayList访问时间: " + (end - start) + " ns");
通常数组的访问速度略快于ArrayList,因为ArrayList有额外的方法调用开销。
2.4 功能丰富性
ArrayList提供了更多便捷的方法:
ArrayList
// 添加元素
languages.add("JavaScript");
// 删除元素
languages.remove("Python");
// 检查包含
boolean hasJava = languages.contains("Java");
// 获取子列表
List
// 遍历(JDK 1.8 lambda)
languages.forEach(System.out::println);
相比之下,数组的功能非常基础,需要手动实现许多操作。
3. 内存管理
数组的内存分配
数组在创建时就分配了连续的内存空间:
int[] largeArray = new int[1000000]; // 立即分配内存
ArrayList的内存管理
ArrayList使用动态扩容策略:
ArrayList
// 当元素数量超过容量时,自动扩容(通常增加50%)
for (int i = 0; i < 100; i++) {
list.add(i);
}
可以通过ensureCapacity方法预先分配空间以提高性能:
ArrayList
optimizedList.ensureCapacity(1000000); // 预先分配大容量
4. 多维结构
多维数组
Java支持真正的多维数组:
int[][] matrix = new int[3][3];
matrix[0][0] = 1;
ArrayList的多维实现
ArrayList需要通过嵌套实现多维结构:
ArrayList
matrixList.add(new ArrayList<>(Arrays.asList(1, 2, 3)));
matrixList.add(new ArrayList<>(Arrays.asList(4, 5, 6)));
5. 原始类型处理
数组处理原始类型
数组可以直接存储原始类型:
int[] primitiveArray = new int[10]; // 不涉及装箱/拆箱
ArrayList处理原始类型
ArrayList只能存储对象,需要使用包装类:
ArrayList
wrapperList.add(5); // 自动装箱
int value = wrapperList.get(0); // 自动拆箱
6. 使用场景建议
适合使用数组的场景
知道确切且不会改变的元素数量需要最高性能的原始类型操作需要多维数据结构与需要数组作为输入的低级API交互
// 图像处理中的像素矩阵
int[][] imagePixels = new int[1024][768];
适合使用ArrayList的场景
元素数量不确定或会变化需要频繁的插入、删除操作需要丰富的集合操作方法需要与其他集合框架组件交互
// 用户动态添加的商品列表
ArrayList
shoppingCart.add(new Product("Laptop", 999.99));
shoppingCart.removeIf(p -> p.getPrice() > 1000); // JDK 1.8新方法
7. 相互转换
数组转ArrayList
String[] array = {"A", "B", "C"};
ArrayList
注意:Arrays.asList()返回的是固定大小的列表,不能添加/删除元素。
ArrayList转数组
ArrayList
String[] arrayFromList = list.toArray(new String[0]);
8. JDK 1.8中的新特性应用
JDK 1.8为ArrayList引入了Stream API支持:
ArrayList
// 使用Stream API过滤和收集
List
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// 并行流处理
int sum = numbers.parallelStream()
.mapToInt(Integer::intValue)
.sum();
结论
特性
数组(Array)
ArrayList
大小
固定
动态可变
类型安全
运行时检查
编译时泛型检查
性能
原始类型操作更快
有额外方法调用开销
功能
基础
丰富的方法集
多维支持
直接支持
需要嵌套实现
原始类型
直接支持
需要使用包装类
内存管理
一次性分配
动态扩容
在选择使用数组还是ArrayList时,应根据具体需求决定。对于性能关键且大小固定的原始类型数据,数组是更好的选择。而对于需要动态大小和丰富操作的情况,ArrayList提供了更多便利。