1. 引言
在 Java 集合框架中,Iterator 是遍历集合元素的标准接口,但它有一个明显的局限------只能单向 遍历。当我们需要在遍历过程中反向移动 、获取上一个元素 或在遍历时安全地修改列表 时,Iterator 就显得力不从心了。
ListIterator 接口正是为解决这些问题而生。它是 Iterator 的子接口,专门为 List 系列集合(如 ArrayList、LinkedList)提供双向遍历 和列表修改 的能力。本文将深入剖析 ListIterator 的源码设计、核心方法、使用场景与注意事项,帮助你彻底掌握这一利器。
2. ListIterator 接口概览
2.1 接口定义
ListIterator 位于 java.util 包中,其接口定义如下:
java
public interface ListIterator<E> extends Iterator<E> {
// 查询操作
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
// 修改操作
void remove();
void set(E e);
void add(E e);
}
2.2 与 Iterator 的关系
ListIterator 继承了 Iterator 的全部方法(hasNext、next、remove),并在此基础上扩展了 6 个新方法。它只能用于 List 及其子类,而 Iterator 可以用于所有 Collection。
| 特性 | Iterator | ListIterator |
|---|---|---|
| 遍历方向 | 单向(向后) | 双向(向前/向后) |
| 获取前一个元素 | 不支持 | 支持(previous()) |
| 获取索引 | 不支持 | 支持(nextIndex()/previousIndex()) |
| 修改元素 | 仅支持删除 | 支持删除、替换、新增 |
| 适用集合 | 所有 Collection | 仅 List |
3. 核心方法详解
3.1 双向遍历方法
ListIterator 最核心的特性就是双向遍历 。与 Iterator 只能通过 next() 向后移动不同,ListIterator 可以通过 previous() 向前移动。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "D"));
ListIterator<String> it = list.listIterator();
// 向后遍历
System.out.println("向后遍历:");
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
// 输出:A B C D
// 此时迭代器位于列表末尾,可以向前遍历
System.out.println("\n向前遍历:");
while (it.hasPrevious()) {
System.out.print(it.previous() + " ");
}
// 输出:D C B A
3.2 索引访问方法
nextIndex() 返回下一次调用 next() 将返回的元素的索引;previousIndex() 返回下一次调用 previous() 将返回的元素的索引。这两个方法在需要定位操作时非常有用。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> it = list.listIterator();
System.out.println("nextIndex: " + it.nextIndex()); // 0
System.out.println("previousIndex: " + it.previousIndex()); // -1
it.next(); // 移动到 "A"
System.out.println("nextIndex: " + it.nextIndex()); // 1
System.out.println("previousIndex: " + it.previousIndex()); // 0
3.3 修改方法:set 与 add
set(E e) 用于替换最近一次 由 next() 或 previous() 返回的元素;add(E e) 用于在当前迭代位置插入新元素。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> it = list.listIterator();
it.next(); // 指向 "A"
it.set("X"); // 将 "A" 替换为 "X"
System.out.println(list); // [X, B, C]
it.add("Y"); // 在 "X" 之后插入 "Y"
System.out.println(list); // [X, Y, B, C]
4. 获取 ListIterator 的三种方式
List 接口提供了三种获取 ListIterator 的方法,各有适用场景:
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 方式一:从列表头部开始
ListIterator<String> it1 = list.listIterator();
// 方式二:从指定索引开始
ListIterator<String> it2 = list.listIterator(1); // 从索引 1(元素 "B")开始
// 方式三:从列表尾部开始(通过 listIterator(size))
ListIterator<String> it3 = list.listIterator(list.size()); // 从末尾开始,可向前遍历
5. 实战案例
5.1 反向遍历列表
java
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// 从末尾获取迭代器,实现反向遍历
ListIterator<Integer> it = numbers.listIterator(numbers.size());
System.out.print("反向遍历:");
while (it.hasPrevious()) {
System.out.print(it.previous() + " ");
}
// 输出:5 4 3 2 1
5.2 遍历时安全地替换元素
java
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
ListIterator<Integer> it = numbers.listIterator();
while (it.hasNext()) {
int num = it.next();
if (num % 2 == 0) {
it.set(num * 10); // 将偶数放大 10 倍
}
}
System.out.println(numbers); // [1, 20, 3, 40, 5]
5.3 在指定位置插入元素
java
List<String> list = new ArrayList<>(Arrays.asList("A", "C"));
ListIterator<String> it = list.listIterator();
it.next(); // 指向 "A"
it.add("B"); // 在 "A" 之后插入 "B"
System.out.println(list); // [A, B, C]
6. 注意事项与常见陷阱
6.1 并发修改异常
与 Iterator 一样,在使用 ListIterator 遍历时,如果通过非迭代器方式 (如 list.add()、list.remove())修改列表,会抛出 ConcurrentModificationException。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String s = it.next();
if (s.equals("B")) {
list.remove("B"); // 错误!应使用 it.remove()
}
}
// 抛出 ConcurrentModificationException
6.2 set 与 remove 的调用限制
set 和 remove 方法都要求最近一次 调用必须是 next() 或 previous()。如果先调用了 add(),再调用 set() 或 remove(),会抛出 IllegalStateException。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "B"));
ListIterator<String> it = list.listIterator();
it.next(); // 合法
it.add("X"); // 插入后,迭代器状态改变
it.set("Y"); // 抛出 IllegalStateException!
6.3 add 方法对迭代器位置的影响
调用 add() 后,新元素被插入到迭代器当前位置之前,且 next() 返回的元素不变,但 previous() 会返回新插入的元素。
java
List<String> list = new ArrayList<>(Arrays.asList("A", "C"));
ListIterator<String> it = list.listIterator();
it.next(); // 返回 "A"
it.add("B"); // 插入 "B"
System.out.println(it.previous()); // 返回 "B"
System.out.println(it.next()); // 返回 "C"
7. 源码视角:ArrayList 中的 ListIterator 实现
以 ArrayList 为例,其内部类 ListItr 实现了 ListIterator 接口。核心字段包括:
java
private class ListItr extends Itr implements ListIterator<E> {
// 游标位置,即下一个 next() 返回元素的索引
private int cursor;
// 最近一次返回元素的索引,-1 表示没有
private int lastRet = -1;
// 期望的修改次数,用于快速失败检测
private int expectedModCount = modCount;
}
add 方法的实现体现了其位置语义:
java
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e); // 在游标位置插入
cursor = i + 1; // 游标后移
lastRet = -1; // 重置 lastRet
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
8. 总结
ListIterator 是 Java 集合框架中一个功能强大的接口,它弥补了 Iterator 单向遍历的不足,提供了双向遍历 、索引访问 和列表修改 三大核心能力。在实际开发中,当你需要反向遍历列表、在遍历时替换或插入元素时,ListIterator 是最佳选择。
掌握 ListIterator 的关键在于理解其游标语义 :next() 与 previous() 如何移动游标、set() 与 remove() 的调用前提、以及 add() 对游标位置的影响。希望本文能帮助你更好地理解和使用这一接口,写出更优雅、更高效的 Java 代码。