杰瑞科技汇

Java List与ArrayList有何区别?

核心摘要(一句话理解)

  • List 是一个接口,它定义了一个列表应该具有的行为(add(), get(), size() 等)。
  • ArrayList 是一个具体的类,它实现了 List 接口,并且是基于动态数组来实现的。

你可以把 List 想象成一份“菜谱”(规定了菜的做法和应有的味道),而 ArrayList 就是按照这份“菜谱”做出来的“一道具体的菜”(有具体的材料和做法)。

Java List与ArrayList有何区别?-图1
(图片来源网络,侵删)

List (接口)

List 是 Java 集合框架(Java Collections Framework)中的一个核心接口,它位于 java.util 包下。

List 的特点:

  1. 是一个接口,不是类:你不能直接创建 List 的实例,它更像是一个“契约”或“蓝图”。
  2. 有序集合List 中的元素是按照你添加的顺序来存储的,它维护着一个插入顺序。
  3. 允许重复元素:你可以向 List 中添加多个相同的元素。
  4. 允许 null 元素:大多数 List 的实现(包括 ArrayList)都允许存储 null 值。
  5. 提供丰富的操作方法:除了从 Collection 接口继承的方法外,List 还提供了许多专门用于操作列表位置的方法,
    • void add(int index, E element): 在指定位置插入一个元素。
    • E get(int index): 获取指定位置的元素。
    • E set(int index, E element): 替换指定位置的元素。
    • E remove(int index): 移除指定位置的元素。

如何使用 List 接口?

你通常使用多态的方式来声明 List 变量,然后在创建实例时选择一个具体的实现类(ArrayList)。

// 正确:使用接口类型声明变量,引用具体实现类的对象
List<String> stringList = new ArrayList<>();
// 也可以使用其他实现类,LinkedList
List<String> anotherList = new LinkedList<>();
// 错误:不能实例化一个接口
// List<String> wrongList = new List<>(); // 编译错误!

为什么推荐使用 List<String> 而不是 ArrayList<String> 声明变量?

  • 解耦:你的代码依赖于 List 这个抽象接口,而不是 ArrayList 这个具体实现,未来如果你想更换为 LinkedList,只需要修改一行代码即可,其他所有使用 stringList 的地方都不用变。
  • 灵活性:可以根据不同的场景选择最高效的实现,如果需要频繁在头部插入/删除元素,LinkedList 可能更高效;如果需要随机访问元素,ArrayList 更高效。
  • 代码可读性:它清晰地表明了你只需要一个“列表”的功能,而不关心其底层实现。

ArrayList (类)

ArrayListList 接口最常用、最著名的一个实现类,它也位于 java.util 包下。

Java List与ArrayList有何区别?-图2
(图片来源网络,侵删)

ArrayList 的特点:

  1. 基于动态数组:它的内部是一个可以动态增长和缩小的数组,当数组空间不足时,它会自动创建一个更大的新数组,并将旧数组中的元素复制到新数组中。
  2. 随机访问快:由于底层是数组,ArrayList 可以通过索引(index)在 O(1) 的时间复杂度内访问任何位置的元素。get(int index) 方法非常快。
  3. 在末尾添加/删除元素较快:在列表末尾添加或删除元素的平均时间复杂度是 O(1)(因为不需要移动大量元素)。
  4. 在中间或头部插入/删除元素慢:如果在列表的中间或头部插入或删除一个元素,所有该位置之后的元素都需要向后或向前移动,时间复杂度为 O(n)n 是列表的大小,这在元素很多时性能会很差。
  5. 非线程安全ArrayList 不是同步的,如果在多线程环境中并发地修改一个 ArrayList,可能会导致数据不一致,如果需要在多线程下使用,应该使用 Collections.synchronizedList(new ArrayList<>()) 或者 CopyOnWriteArrayList
  6. 内存占用相对较小:相比于 LinkedListArrayList 只需要一个连续的内存空间来存储元素,没有额外的“节点”开销。

List vs ArrayList 对比总结表

特性 List ArrayList
本质 接口 (Interface) (Class)
实例化 不能被实例化 可以被实例化
作用 定义列表的通用行为和契约 List 接口的一种具体实现
底层结构 不指定,由具体实现类决定 动态数组
性能特点 不适用(是抽象概念) 随机访问快 (O(1))中间插入/删除慢 (O(n))
内存占用 不适用 较小,只需一个连续数组
线程安全 不适用 非线程安全
使用场景 当你只关心“列表”这个抽象概念,不关心具体实现时 当你需要一个随机访问频繁在末尾增删多、且在单线程环境下工作的列表时

何时使用 List,何时使用 ArrayList

这是一个非常好的问题,答案其实很简单:

何时使用 List (接口引用)?

几乎总是应该用 List 来声明你的变量。

// 方法参数
public void processList(List<String> items) {
    // ...
}
// 成员变量
private List<User> users;
// 局部变量
List<Product> products = new ArrayList<>();

原因:遵循面向对象设计的依赖倒置原则,让你的代码依赖于抽象(List 接口),而不是具体实现(ArrayList 类),这让你的代码更灵活、更易于维护和扩展。

何时使用 ArrayList (具体实现)?

当你需要创建一个列表对象时,你才需要选择具体的实现类,选择 ArrayList 的场景通常是:

Java List与ArrayList有何区别?-图3
(图片来源网络,侵删)
  1. 需要频繁通过索引访问元素:循环遍历并读取元素,或者根据下标快速查找。

    List<String> names = new ArrayList<>();
    names.add("Alice");
    names.add("Bob");
    // 快速获取第二个元素
    String secondName = names.get(1); // 非常快
  2. 主要在列表末尾添加或删除元素:缓存、收集日志等场景。

    List<String> logs = new ArrayList<>();
    logs.add("INFO: System started.");
    logs.add("WARNING: Low memory.");
    // 在末尾添加很快
    logs.add("ERROR: Critical failure.");
  3. 内存敏感的场景ArrayList 的内存开销通常比 LinkedList 小。

什么时候不使用 ArrayList

当你有这些需求时,应该考虑 List 的其他实现类,LinkedList

  • 需要频繁在列表的中间或头部进行插入和删除操作
  • 需要使用 List 作为队列(Queue)或双端队列(Deque),频繁地在一端添加,在另一端删除。

代码示例

import java.util.ArrayList;
import java.util.List;
public class ListVsArrayListExample {
    public static void main(String[] args) {
        // 1. 使用 List 接口声明,ArrayList 实现
        List<String> fruits = new ArrayList<>();
        // 2. 添加元素 (List 接口的方法)
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        System.out.println("Initial list: " + fruits); // [Apple, Banana, Orange]
        // 3. 在指定位置插入元素 (List 接口的方法)
        fruits.add(1, "Mango");
        System.out.println("After adding Mango: " + fruits); // [Apple, Mango, Banana, Orange]
        // 4. 获取元素 (ArrayList 的优势,随机访问快)
        String secondFruit = fruits.get(1);
        System.out.println("Second fruit: " + secondFruit); // Mango
        // 5. 替换元素 (List 接口的方法)
        fruits.set(2, "Grape");
        System.out.println("After replacing: " + fruits); // [Apple, Mango, Grape, Orange]
        // 6. 移除元素 (在中间移除,ArrayList 的劣势,需要移动元素)
        fruits.remove(1);
分享:
扫描分享到社交APP
上一篇
下一篇