源本科技 | 码上会

Java List

2026/01/23
76
0

学习目标

  • 理解 List 接口的核心特性:有序、可重复、支持索引访问

  • 掌握 List 的常用操作方法(增删改查、遍历、搜索)

  • 熟悉主要实现类(ArrayListLinkedListVector)的性能差异与适用场景

  • 能够根据业务需求选择合适的 List 实现

  • 区分 ListSet 的本质区别


List 接口概述

List 是 Java 集合框架中最重要的接口之一,继承自 Collection 接口。它代表一个有序的元素序列,具有以下关键特性:

  • 维护插入顺序:元素按添加顺序存储

  • 允许重复元素:同一个对象可多次出现

  • 支持索引访问:可通过整数下标获取或修改元素

  • 允许 null 值(具体行为依赖实现类)

  • 支持双向遍历:通过 ListIterator 可前后移动

接口声明

public interface List<E> extends Collection<E>

注意:不能直接实例化 List,必须使用其实现类(如 ArrayList)创建对象。

List<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");

主要实现类

实现类

底层结构

随机访问

插入 / 删除

线程安全

适用场景

ArrayList

动态数组

O(1)

O(n)
(中间操作需移动元素)

❌ 否

频繁读取、尾部操作

LinkedList

双向链表

O(n)

O(1)
(已知节点位置时)

❌ 否

频繁在首尾或中间插入 / 删除

Vector

同步动态数组

O(1)

O(n)

✅ 是
(全方法加锁)

遗留代码,不推荐新项目使用

Stack

继承 Vector

O(1)

O(n)

✅ 是

LIFO 栈操作
(官方建议用 ArrayDeque 替代)

性能提示

  • 若需频繁通过索引访问 → 选 ArrayList

  • 若需频繁在列表中间增删 → 选 LinkedList

  • 高并发场景 → 考虑 Collections.synchronizedList() 或并发集合


核心操作

1. 添加元素

List<String> list = new ArrayList<>();

// 尾部添加
list.add("Java");        // [Java]

// 指定位置插入(后续元素右移)
list.add(0, "Python");   // [Python, Java]

// 批量添加
list.addAll(Arrays.asList("C++", "Go")); // [Python, Java, C++, Go]

注意:在索引 i 处插入要求 0 ≤ i ≤ size()。若 i == size(),等价于尾部添加;若 i > size(),抛出 IndexOutOfBoundsException


2. 更新元素

list.set(1, "JavaScript"); // 将索引 1 的元素替换为 "JavaScript"
// 结果: [Python, JavaScript, C++, Go]
  • 时间复杂度:O(1)(直接通过索引定位)


3. 搜索元素

int firstIndex = list.indexOf("C++");     // 返回 2
int lastIndex  = list.lastIndexOf("C++"); // 返回 2(无重复时相同)

int notFound = list.indexOf("Rust");      // 返回 -1
  • indexOf():返回首次出现的索引

  • lastIndexOf():返回最后一次出现的索引


4. 删除元素

// 按索引删除
list.remove(0); // 删除 "Python",后续元素左移 → [JavaScript, C++, Go]

// 按值删除(仅删除第一个匹配项)
list.remove("C++"); // → [JavaScript, Go]

注意:remove(Object)remove(int) 是重载方法。若传入 Integer,可能因自动装箱导致歧义,建议显式转换:

list.remove((Object) 123); // 删除值为 123 的元素
list.remove(0);           // 删除索引 0 的元素

5. 访问元素

String first = list.get(0);
String last  = list.get(list.size() - 1);
  • List 及其子类型支持此操作,Collection 接口不提供


6. 其他常用查询

boolean hasJS = list.contains("JavaScript"); // true
boolean empty = list.isEmpty();              // false
int count = list.size();                     // 2

遍历方式

方式一:传统 for 循环

for (int i = 0; i < list.size(); i++) {
    System.out.println(i + ": " + list.get(i));
}

方式二:增强 for-each 循环

for (String lang : list) {
    System.out.println(lang);
}

方式三:Iterator

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String lang = it.next();
    if (lang.equals("Go")) {
        it.remove(); // 安全删除
    }
}

方式四:Stream

list.stream()
    .filter(lang -> lang.startsWith("J"))
    .forEach(System.out::println);

List 接口方法

方法

描述

void add(int index, E element)

在指定位置插入元素

boolean add(E e)

尾部添加元素

boolean addAll(Collection<? extends E> c)

尾部批量添加

boolean addAll(int index, Collection<? extends E> c)

指定位置批量插入

E get(int index)

获取指定索引元素

E set(int index, E element)

替换指定索引元素,返回旧值

E remove(int index)

删除指定索引元素,返回被删元素

boolean remove(Object o)

删除第一个匹配元素

int indexOf(Object o)

返回首次出现索引,未找到返回 -1

int lastIndexOf(Object o)

返回最后一次出现索引

ListIterator<E> listIterator()

返回支持双向遍历的迭代器

List<E> subList(int from, int to)

返回视图子列表(左闭右开)

void sort(Comparator<? super E> c)

按比较器排序(Java 8+)

boolean contains(Object o)

是否包含元素

int size()

元素数量

boolean isEmpty()

是否为空


List 与 Set

特性

List

Set

顺序性

有序(插入序)

无序(LinkedHashSet 除外)

重复元素

✅ 允许

❌ 不允许

索引访问

✅ 支持 get(index)

❌ 不支持

null 元素

可多个

最多一个(HashSet

典型实现

ArrayList, LinkedList

HashSet, TreeSet

适用场景

保留顺序、允许重复的数据序列

去重、成员检查


性能复杂度

操作

ArrayList

LinkedList

尾部 add()

O(1)(均摊)

O(1)

中间 add(index)

O(n)

O(n)(需遍历到位置)

get(index)

O(1)

O(n)

remove(index)

O(n)

O(n)

remove(value)

O(n)

O(n)

内存占用

较低(仅数组)

较高(每个节点存前后指针)

经验法则

  • 默认使用 ArrayList

  • 仅当频繁在列表开头或中间插入 / 删除不依赖随机访问时,考虑 LinkedList


重点总结

  • List有序、可重复、支持索引访问的集合

  • 核心实现:ArrayList(数组,快随机访问)、LinkedList(链表,快增删)

  • 提供 add(index, e)get(index)set(index, e)remove(index) 等索引操作

  • 支持 indexOf() / lastIndexOf() 搜索、subList() 视图等高级功能

  • Set 的根本区别在于是否允许重复是否有序


思考题

  1. 为什么 LinkedList 在已知节点位置时插入 / 删除是 O(1),但 list.add(1000, "X") 仍是 O(n)?请结合其内部结构解释。

  2. ArrayList 的扩容机制是怎样的?默认初始容量是多少?如何避免频繁扩容带来的性能损耗?

  3. 假设你需要实现一个“最近使用记录”功能,最多保存 100 条,新记录插入头部,超出时删除尾部。你会选择哪种 List 实现?为什么?