你真的了解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 intList = new ArrayList<>(); // 初始容量为10的空列表

ArrayList stringList = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));

2. 核心区别

2.1 大小固定性

数组的大小在创建时就固定了,无法动态改变。

int[] fixedArray = new int[3];

fixedArray[0] = 1;

fixedArray[1] = 2;

fixedArray[2] = 3;

// fixedArray[3] = 4; // 抛出ArrayIndexOutOfBoundsException

ArrayList的大小是动态的,会根据需要自动增长。

ArrayList dynamicList = new 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 = new 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 = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));

// 添加元素

languages.add("JavaScript");

// 删除元素

languages.remove("Python");

// 检查包含

boolean hasJava = languages.contains("Java");

// 获取子列表

List subList = languages.subList(1, 3);

// 遍历(JDK 1.8 lambda)

languages.forEach(System.out::println);

相比之下,数组的功能非常基础,需要手动实现许多操作。

3. 内存管理

数组的内存分配

数组在创建时就分配了连续的内存空间:

int[] largeArray = new int[1000000]; // 立即分配内存

ArrayList的内存管理

ArrayList使用动态扩容策略:

ArrayList list = new ArrayList<>(); // 初始容量10

// 当元素数量超过容量时,自动扩容(通常增加50%)

for (int i = 0; i < 100; i++) {

list.add(i);

}

可以通过ensureCapacity方法预先分配空间以提高性能:

ArrayList optimizedList = new ArrayList<>();

optimizedList.ensureCapacity(1000000); // 预先分配大容量

4. 多维结构

多维数组

Java支持真正的多维数组:

int[][] matrix = new int[3][3];

matrix[0][0] = 1;

ArrayList的多维实现

ArrayList需要通过嵌套实现多维结构:

ArrayList> matrixList = new 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 = new ArrayList<>(); // 存储Integer对象

wrapperList.add(5); // 自动装箱

int value = wrapperList.get(0); // 自动拆箱

6. 使用场景建议

适合使用数组的场景

知道确切且不会改变的元素数量需要最高性能的原始类型操作需要多维数据结构与需要数组作为输入的低级API交互

// 图像处理中的像素矩阵

int[][] imagePixels = new int[1024][768];

适合使用ArrayList的场景

元素数量不确定或会变化需要频繁的插入、删除操作需要丰富的集合操作方法需要与其他集合框架组件交互

// 用户动态添加的商品列表

ArrayList shoppingCart = new 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 listFromArray = new ArrayList<>(Arrays.asList(array));

注意:Arrays.asList()返回的是固定大小的列表,不能添加/删除元素。

ArrayList转数组

ArrayList list = new ArrayList<>(Arrays.asList("X", "Y", "Z"));

String[] arrayFromList = list.toArray(new String[0]);

8. JDK 1.8中的新特性应用

JDK 1.8为ArrayList引入了Stream API支持:

ArrayList numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));

// 使用Stream API过滤和收集

List evenNumbers = numbers.stream()

.filter(n -> n % 2 == 0)

.collect(Collectors.toList());

// 并行流处理

int sum = numbers.parallelStream()

.mapToInt(Integer::intValue)

.sum();

结论

特性

数组(Array)

ArrayList

大小

固定

动态可变

类型安全

运行时检查

编译时泛型检查

性能

原始类型操作更快

有额外方法调用开销

功能

基础

丰富的方法集

多维支持

直接支持

需要嵌套实现

原始类型

直接支持

需要使用包装类

内存管理

一次性分配

动态扩容

在选择使用数组还是ArrayList时,应根据具体需求决定。对于性能关键且大小固定的原始类型数据,数组是更好的选择。而对于需要动态大小和丰富操作的情况,ArrayList提供了更多便利。