核心区别一览表
| 特性 | 数组 | List (接口) |
|---|---|---|
| 数据类型 | 固定长度,创建后不能改变大小。 | 动态长度,可以随时添加或删除元素。 |
| 数据类型 | 可以存储基本数据类型(如 int, char)和对象引用。 |
只能存储对象引用,存储基本类型时需要使用其包装类(如 Integer, Character)。 |
| 性能 | 访问(get)性能极高,内存是连续的,可通过索引直接计算地址。 | 访问性能略低于数组,通常基于数组实现(如 ArrayList),但多了一层方法调用的开销。 |
| 功能丰富性 | 功能非常有限,只提供基本的 length 属性和 [] 访问。 |
功能非常丰富,提供 add(), remove(), size(), sort(), stream() 等大量方法。 |
| 泛型支持 | 支持,但语法略有不同(如 String[])。 |
完美支持,是 Java 集合框架的核心,泛型是其设计初衷。 |
| 内存分配 | 在堆上分配连续的内存块。 | ArrayList 在内部维护一个数组,当容量不足时会创建一个更大的数组并复制数据。LinkedList 则是节点式内存。 |
| 继承关系 | 是 Java 中的一个对象,但不是集合框架的一部分。 | 是 java.util.Collection 接口的子接口,属于集合框架的一部分。 |
详细解释与代码示例
长度固定 vs. 动态扩展
这是两者最根本、最直观的区别。
-
数组:长度在创建时就必须指定,并且之后无法改变。
// 创建一个长度为 3 的字符串数组 String[] names = new String[3]; names[0] = "Alice"; names[1] = "Bob"; names[2] = "Charlie"; // names[3] = "David"; // 这行代码会抛出 ArrayIndexOutOfBoundsException 异常
-
List:长度是动态的,可以随时添加或删除元素。
// 创建一个 ArrayList,初始容量通常是 10(但大小 size() 为 0) List<String> namesList = new ArrayList<>(); System.out.println("Initial size: " + namesList.size()); // 输出 0 namesList.add("Alice"); namesList.add("Bob"); namesList.add("Charlie"); System.out.println("Size after adding 3: " + namesList.size()); // 输出 3 namesList.add("David"); // 完全没问题,List 会自动扩容 System.out.println("Size after adding 1 more: " + namesList.size()); // 输出 4
存储基本类型 vs. 对象引用
-
数组:可以直接存储基本数据类型,无需任何包装。
int[] primitiveArray = new int[100]; // 直接存储 100 个 int double[] doubleArray = new double[50]; // 直接存储 50 个 double
-
List:不能直接存储基本类型,必须使用它们的包装类。
// List<int> list = new ArrayList<>(); // 编译错误!泛型参数不能是基本类型 List<Integer> integerList = new ArrayList<>(); // 正确,使用 Integer 包装类 integerList.add(10); // Java 会自动将 int 10 装箱成 Integer 对象 int number = integerList.get(0); // Java 会自动将 Integer 对象拆箱成 int
注意:这种自动装箱/拆箱会带来微小的性能开销,并且在处理大量数据时可能会影响内存(因为对象比基本类型占用更多空间)。
性能对比
-
随机访问:
- 数组:性能最优。
array[i]的操作是通过索引直接计算内存地址,时间复杂度为 O(1)。 - List:取决于其实现类。
ArrayList:性能也很好,因为它内部就是用数组实现的。list.get(i)的时间复杂度也是 O(1),但比原生数组访问多了一次方法调用。LinkedList:性能较差。list.get(i)需要从头或尾节点开始遍历,时间复杂度为 O(n)。
- 数组:性能最优。
-
在中间插入/删除:
- 数组:性能极差,如果在中间插入一个元素,该位置之后的所有元素都需要向后移动,时间复杂度为 O(n),删除同理。
- List:取决于其实现类。
ArrayList:性能较差,原因同上,需要移动元素,时间复杂度为 O(n)。LinkedList:性能很好,只需要修改前后节点的指针即可,时间复杂度为 O(1)。
功能丰富性
List 作为集合框架的一员,提供了远比数组丰富和强大的功能。
-
数组:只有
length属性和[]访问。 -
List:拥有大量实用方法,
List<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Orange"); // 1. 获取大小 int size = fruits.size(); // 2. 检查是否包含某个元素 boolean hasApple = fruits.contains("Apple"); // 3. 按索引删除 fruits.remove(1); // 移除 "Banana" // 4. 按值删除 fruits.remove("Orange"); // 5. 排序 (需要传入 Comparator) Collections.sort(fruits); // 或者 fruits.sort(null); // 6. 转换成流进行函数式操作 fruits.stream() .filter(f -> f.startsWith("A")) .forEach(System.out::println);
泛型与类型安全
两者都支持泛型,但 List 的泛型支持是其核心设计,使得代码更安全、更清晰。
-
数组:
String[] strArray = new String[10]; // strArray[0] = 123; // 编译错误!类型安全检查
-
List:
List<String> strList = new ArrayList<>(); // strList.add(123); // 编译错误!类型安全检查
如何选择?
了解了区别后,在实际开发中如何选择呢?
| 场景 | 推荐选择 | 理由 |
|---|---|---|
| 数量固定、已知,且对性能要求极高(如游戏中的坐标点、图像像素)。 | 数组 | 固定长度、内存连续、访问速度快,没有额外开销。 |
| 数量不固定或可能变化,需要频繁增删元素。 | List | 动态扩容/缩容功能是刚需。 |
| 需要丰富的数据处理功能(排序、查找、转换、过滤等)。 | List | 集合框架提供了大量现成的方法,代码更简洁、可读性更高。 |
| 需要频繁在中间插入/删除元素,且不关心随机访问性能。 | LinkedList |
插入和删除性能为 O(1)。 |
| 需要频繁进行随机访问(按索引取值),且元素数量变化不频繁。 | ArrayList |
访问性能好,是 List 最常用的实现。 |
| 存储大量基本类型数据,且对内存和性能非常敏感。 | 数组 | 避免了自动装箱带来的开销和内存占用。 |
一句话概括:数组是一个低级、高效但功能受限的固定大小容器;而 List 是一个高级、灵活、功能强大的动态大小容器,是 Java 集合框架的基石。
在现代 Java 开发中,除非你有非常特殊且明确的理由(如性能瓶颈、固定大小、存储基本类型),否则应该优先使用 List,特别是 ArrayList,它能提供更好的代码可读性、可维护性和功能扩展性。
