杰瑞科技汇

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

核心区别一览表

特性 数组 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,它能提供更好的代码可读性、可维护性和功能扩展性。

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