- 数组是 Java 语言内置的静态数据结构,它的长度在创建时就固定了,并且可以存储基本数据类型(如
int,char)和对象。 List是 Java 集合框架中的一个接口,代表一种动态的、可调整大小的动态数据结构,它只能存储对象(包括基本类型的包装类,如Integer,Character)。
下面我们从多个维度进行详细对比。

核心区别对比表
| 特性 | 数组 | List (接口) |
|---|---|---|
| 本质/类型 | Java 语言内置的静态数据结构。 | Java 集合框架中的一个接口。 |
| 长度 | 固定,创建时必须指定长度,之后无法改变。 | 可变,可以根据需要动态地添加或删除元素。 |
| 数据类型 | 可以存储基本数据类型(int, double)和对象引用。 |
只能存储对象引用,不能直接存基本类型,需要使用其包装类(如 Integer, Double)。 |
| 性能 | 访问快,通过索引访问元素(arr[0])是 O(1) 时间复杂度,因为内存是连续分配的。增删慢,在数组中间增删元素需要移动大量元素,是 O(n) 时间复杂度。 |
增删相对快。ArrayList 的增删在末尾是 O(1)(均摊),在中间是 O(n)。访问稍慢。 ArrayList 的访问是 O(1),但 LinkedList 的访问是 O(n)。 |
| 功能/方法 | 功能非常有限,只有 length 属性和一些 java.lang.reflect.Array 中的工具方法。 |
功能非常丰富,提供了大量方法,如 add(), remove(), get(), size(), sort(), iterator() 等。 |
| 泛型支持 | 不支持泛型,数组在创建时会进行类型检查,但编译后类型信息会被擦除(运行时是 Object[])。 |
完美支持泛型,可以在编译时进行严格的类型检查,提高代码安全性。 |
| 初始化 | int[] arr = new int[5];int[] arr2 = {1, 2, 3}; |
List<Integer> list = new ArrayList<>();List<Integer> list2 = List.of(1, 2, 3); (Java 9+) |
| 多维结构 | 原生支持,可以直接定义 int[][] 这样的二维数组。 |
不支持原生多维,但可以通过 List<List<Integer>> 的方式实现,使用起来更灵活。 |
详细解释
长度:固定 vs. 可变
这是两者最直观的区别。
-
数组:
int[] numbers = new int[10]; // 长度固定为10 numbers[10] = 100; // 运行时会抛出 ArrayIndexOutOfBoundsException // numbers[11] = 200; // 不可能,因为数组长度就是10 // numbers.length = 11; // 编译错误,final 属性,无法修改
数组的长度是
final的,一旦确定,无法改变,如果需要更多空间,必须创建一个更大的新数组,然后将旧数组的内容复制过去。 -
List:
(图片来源网络,侵删)List<String> names = new ArrayList<>(); System.out.println(names.size()); // 输出 0 names.add("Alice"); // 动态添加 names.add("Bob"); System.out.println(names.size()); // 输出 2 names.remove(0); // 动态删除 System.out.println(names.size()); // 输出 1List的size()方法会返回当前元素的数量,可以随时变化。
数据类型:基本类型 vs. 对象
-
数组:
int[] primitiveArray = new int[5]; // 可以直接存储基本类型 int String[] objectArray = new String[5]; // 也可以存储对象 String
这是数组的一大优势,在处理大量数值数据时,避免了基本类型和其包装类之间的装箱/拆箱操作,性能更好,内存占用也更小。
-
List:
(图片来源网络,侵删)// List<int> list = new ArrayList<>(); // 编译错误!泛型参数不能是基本类型 List<Integer> list = new ArrayList<>(); // 必须使用包装类 Integer
List必须存储对象,当你想存储int时,必须使用Integer,这会带来一个副作用:自动装箱和拆箱,在list.add(10)时,JVM 会自动将int类型的10装箱成Integer对象;在int num = list.get(0)时,又会将Integer对象拆箱成int,这个过程会有轻微的性能开销。
性能:访问 vs. 增删
-
数组:
- 访问:极快,因为数组在内存中是连续存储的,通过索引计算内存地址是直接寻址,时间复杂度为 O(1)。
- 增删:很慢,如果在数组中间插入或删除一个元素,该元素之后的所有元素都需要向前或向后移动,时间复杂度为 O(n)。
-
List (取决于其实现类):
ArrayList:- 访问:很快,底层是基于数组实现的,所以通过索引访问也是 O(1)。
- 增删:在末尾添加/删除元素很快(均摊 O(1)),因为不需要移动元素,但在中间增删元素很慢(O(n)),因为同样需要移动元素。
LinkedList:- 访问:很慢,底层是基于双向链表实现的,访问第 i 个元素需要从头或尾开始遍历,时间复杂度为 O(n)。
- 增删:在已知位置增删元素很快(O(1)),因为只需要修改前后节点的指针即可,但如果要找到这个位置,仍然需要 O(n) 的时间。
功能和泛型
-
数组:
- 功能非常基础,没有
add(),remove()等便捷方法。 - 泛型支持较弱,虽然可以写成
String[] strings = new String[10];,但在运行时,数组的类型信息会丢失,strings.getClass().getComponentType()会返回Object.class。
- 功能非常基础,没有
-
List:
- 功能强大。
List接口及其实现类(如ArrayList,LinkedList)提供了丰富的方法,可以轻松进行排序、查找、迭代等操作。 - 泛型支持完美。
List<String>明确表示这个列表只能存放String对象,编译器会进行检查,有效避免了ClassCastException。
- 功能强大。
何时使用数组?
- 性能要求极高:当你处理大量数值数据(如像素点、坐标、物理模拟等),并且对内存和访问速度有极致要求时,使用数组可以避免装箱开销和
List的额外方法调用开销。 - 长度固定且已知:如果你明确知道需要存储多少个元素,并且这个数量永远不会改变,数组是一个简单高效的选择。
- 需要与底层 API 或遗留代码交互:一些 Java Native Interface (JNI) 或旧版 API 可能直接要求使用数组作为参数。
何时使用 List?
在绝大多数现代 Java 应用程序中,应该优先使用 List。
- 长度不确定或会变化:这是最常见的原因,如果你需要动态地管理一组数据,
List是不二之选。 - 需要丰富的操作:如果你需要对数据进行排序、查找、过滤、转换等复杂操作,
List提供了Collections工具类和 Stream API 等强大支持。 - 类型安全:使用泛型
List<T>可以在编译时保证类型安全,减少运行时错误。 - 代码可读性和维护性:
List的 API 更加清晰和现代化,代码意图更明确。
| 数组 | List | |
|---|---|---|
| 一句话总结 | 静态的、原始的、高性能的“容器” | 动态的、功能丰富的、类型安全的“集合” |
| 核心优势 | 内存紧凑、访问速度快、能存基本类型 | 灵活、功能强大、类型安全、易于扩展 |
| 核心劣势 | 长度固定、功能匮乏、不灵活 | 不能存基本类型、有轻微性能开销(自动装箱) |
最终建议:除非你有非常明确的理由(如上述的“何时使用数组”场景),否则在开发中请优先使用 ArrayList,它结合了数组的快速访问和动态长度的优点,是绝大多数情况下的最佳选择。
