杰瑞科技汇

Java数组与List的核心区别是什么?

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

下面我们从多个维度进行详细对比。

Java数组与List的核心区别是什么?-图1
(图片来源网络,侵删)

核心区别对比表

特性 数组 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

    Java数组与List的核心区别是什么?-图2
    (图片来源网络,侵删)
    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()); // 输出 1

    Listsize() 方法会返回当前元素的数量,可以随时变化。

数据类型:基本类型 vs. 对象

  • 数组

    int[] primitiveArray = new int[5]; // 可以直接存储基本类型 int
    String[] objectArray = new String[5]; // 也可以存储对象 String

    这是数组的一大优势,在处理大量数值数据时,避免了基本类型和其包装类之间的装箱/拆箱操作,性能更好,内存占用也更小。

  • List

    Java数组与List的核心区别是什么?-图3
    (图片来源网络,侵删)
    // 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

何时使用数组?

  1. 性能要求极高:当你处理大量数值数据(如像素点、坐标、物理模拟等),并且对内存和访问速度有极致要求时,使用数组可以避免装箱开销和 List 的额外方法调用开销。
  2. 长度固定且已知:如果你明确知道需要存储多少个元素,并且这个数量永远不会改变,数组是一个简单高效的选择。
  3. 需要与底层 API 或遗留代码交互:一些 Java Native Interface (JNI) 或旧版 API 可能直接要求使用数组作为参数。

何时使用 List?

在绝大多数现代 Java 应用程序中,应该优先使用 List

  1. 长度不确定或会变化:这是最常见的原因,如果你需要动态地管理一组数据,List 是不二之选。
  2. 需要丰富的操作:如果你需要对数据进行排序、查找、过滤、转换等复杂操作,List 提供了 Collections 工具类和 Stream API 等强大支持。
  3. 类型安全:使用泛型 List<T> 可以在编译时保证类型安全,减少运行时错误。
  4. 代码可读性和维护性List 的 API 更加清晰和现代化,代码意图更明确。
数组 List
一句话总结 静态的、原始的、高性能的“容器” 动态的、功能丰富的、类型安全的“集合”
核心优势 内存紧凑、访问速度快、能存基本类型 灵活、功能强大、类型安全、易于扩展
核心劣势 长度固定、功能匮乏、不灵活 不能存基本类型、有轻微性能开销(自动装箱)

最终建议:除非你有非常明确的理由(如上述的“何时使用数组”场景),否则在开发中请优先使用 ArrayList,它结合了数组的快速访问和动态长度的优点,是绝大多数情况下的最佳选择。

分享:
扫描分享到社交APP
上一篇
下一篇