ListIterator 是 Java 集合框架中专门为 List 接口设计的增强型迭代器。与普通 Iterator 相比,它提供了双向遍历能力和更丰富的修改操作,是处理 List 集合时不可或缺的工具。
在实际开发中,我经常遇到需要双向遍历列表或者在遍历过程中动态修改列表内容的需求。比如在开发一个日志分析工具时,需要前后查看日志条目;或者在实现撤销/重做功能时,需要来回遍历操作记录。这些场景下,ListIterator 都展现出了其独特的价值。
注意:ListIterator 只能用于 List 及其实现类(如 ArrayList、LinkedList),不能用于 Set 或 Map 等其他集合类型。
ListIterator 最显著的特点就是支持双向遍历。这意味着我们不仅可以像普通 Iterator 那样从前向后遍历,还能从后向前遍历,这在很多算法实现中非常有用。
java复制List<String> languages = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
ListIterator<String> it = languages.listIterator();
// 正向遍历
while(it.hasNext()) {
System.out.println("正向: " + it.next());
}
// 反向遍历
while(it.hasPrevious()) {
System.out.println("反向: " + it.previous());
}
在实际项目中,我常用这种双向遍历来实现诸如:
ListIterator 提供了 nextIndex() 和 previousIndex() 方法,可以精确获取当前遍历位置的前后索引。
java复制List<Integer> numbers = Arrays.asList(10, 20, 30, 40);
ListIterator<Integer> it = numbers.listIterator();
while(it.hasNext()) {
int currentIndex = it.nextIndex();
int value = it.next();
System.out.println("索引[" + currentIndex + "] = " + value);
}
这个特性在需要基于位置进行特殊处理的场景下特别有用。比如我在开发一个表格处理工具时,就利用这个功能实现了隔行变色效果。
与普通 Iterator 只能删除元素不同,ListIterator 允许在遍历过程中添加、修改和删除元素。
java复制List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
ListIterator<String> it = fruits.listIterator();
it.next(); // Apple
it.set("Red Apple"); // 修改当前元素
it.next(); // Banana
it.add("Orange"); // 在Banana后添加新元素
it.previous(); // 回到Orange
it.remove(); // 删除Orange
重要提示:修改操作有严格的调用顺序限制。必须在调用 next() 或 previous() 后才能调用 set() 或 remove(),否则会抛出 IllegalStateException。
利用 ListIterator 的双向特性,可以高效实现列表反转:
java复制public static <T> void reverse(List<T> list) {
ListIterator<T> forward = list.listIterator();
ListIterator<T> backward = list.listIterator(list.size());
for(int i=0, mid=list.size()/2; i<mid; i++) {
T temp = forward.next();
forward.set(backward.previous());
backward.set(temp);
}
}
这个实现比使用 Collections.reverse() 更灵活,因为它允许我们在反转过程中对元素进行额外处理。
结合双向遍历和修改能力,可以实现复杂的条件过滤:
java复制public static void processNumbers(List<Integer> numbers) {
ListIterator<Integer> it = numbers.listIterator();
// 第一遍:正向处理
while(it.hasNext()) {
int num = it.next();
if(num % 2 == 0) {
it.set(num * 10); // 偶数放大10倍
}
}
// 第二遍:反向处理
while(it.hasPrevious()) {
int num = it.previous();
if(num % 5 == 0) {
it.remove(); // 删除5的倍数
} else if(num > 100) {
it.add(num / 2); // 大数前插入一半值
}
}
}
这种多遍处理模式在实际业务逻辑中很常见,比如数据清洗、报表生成等场景。
| 操作 | ArrayList | LinkedList |
|---|---|---|
| next() | O(1) | O(1) |
| previous() | O(1) | O(n) |
| add() | O(n) | O(1) |
| remove() | O(n) | O(1) |
| set() | O(1) | O(1) |
从表格可以看出,对于 LinkedList,previous() 操作性能较差,因为需要从头开始遍历。因此在使用 LinkedList 时,应尽量避免频繁的 previous() 调用。
ListIterator 对并发修改有严格限制。如果在迭代过程中,列表被其他迭代器或直接修改,会抛出 ConcurrentModificationException。
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
ListIterator<String> it1 = list.listIterator();
ListIterator<String> it2 = list.listIterator();
it1.next();
it1.add("X"); // 通过it1修改列表
it2.next(); // 抛出ConcurrentModificationException
在实际项目中,我建议:
每个 ListIterator 都会维护一些状态信息,包括:
对于大型列表,创建多个 ListIterator 可能会消耗较多内存。在内存敏感的场景下,应该重用迭代器而不是频繁创建新的。
这是使用 ListIterator 时最常见的异常,通常是由于不正确的调用顺序导致的。
错误示例:
java复制ListIterator<String> it = list.listIterator();
it.remove(); // 抛出IllegalStateException
正确做法:
java复制ListIterator<String> it = list.listIterator();
if(it.hasNext()) {
it.next(); // 必须先移动游标
it.remove(); // 现在可以安全调用
}
添加或删除元素会影响后续元素的索引,这点在编写算法时要特别注意。
java复制List<Integer> nums = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> it = nums.listIterator();
it.next(); // 1
it.next(); // 2
it.add(5); // 在2后面添加5
// 现在列表是[1,2,5,3,4]
// 此时it.previous()返回5,不是2
在实现双向遍历算法时,要特别注意边界条件:
java复制ListIterator<String> it = list.listIterator();
// 检查是否可前移
if(it.hasPrevious()) {
String prev = it.previous();
// 处理prev
}
// 检查是否可后移
if(it.hasNext()) {
String next = it.next();
// 处理next
}
在开发简易文本编辑器时,ListIterator 非常适合用来管理编辑历史:
java复制public class TextEditor {
private List<String> history = new ArrayList<>();
private ListIterator<String> historyIterator = history.listIterator();
public void executeCommand(String cmd) {
historyIterator.add(cmd);
// 确保迭代器位于最新位置
while(historyIterator.hasNext()) {
historyIterator.next();
}
}
public boolean undo() {
if(historyIterator.hasPrevious()) {
String cmd = historyIterator.previous();
// 执行撤销逻辑...
return true;
}
return false;
}
public boolean redo() {
if(historyIterator.hasNext()) {
String cmd = historyIterator.next();
// 执行重做逻辑...
return true;
}
return false;
}
}
在数据迁移过程中,经常需要前后查看数据记录:
java复制public class DataMigrator {
public void migrate(List<Record> source, List<Record> target) {
ListIterator<Record> srcIt = source.listIterator();
ListIterator<Record> tgtIt = target.listIterator();
while(srcIt.hasNext() && tgtIt.hasNext()) {
Record srcRecord = srcIt.next();
Record tgtRecord = tgtIt.next();
if(!validate(srcRecord, tgtRecord)) {
// 验证失败,回退一步检查
srcIt.previous();
tgtIt.previous();
handleConflict(srcIt, tgtIt);
}
}
}
// 其他方法...
}
| 特性 | Iterator | ListIterator |
|---|---|---|
| 遍历方向 | 单向 | 双向 |
| 修改操作 | 仅remove | add/remove/set |
| 索引支持 | 无 | 有 |
| 起始位置 | 只能从开始 | 可指定位置 |
| 适用性 | 所有Collection | 仅List |
对于简单遍历,传统的 for 循环可能更直观:
java复制// 传统for循环
for(int i=0; i<list.size(); i++) {
String item = list.get(i);
// 处理item
}
// ListIterator方式
ListIterator<String> it = list.listIterator();
while(it.hasNext()) {
String item = it.next();
// 处理item
}
选择建议:
在某些特殊场景下,我们可以实现自己的ListIterator。比如实现一个只读的ListIterator:
java复制public class UnmodifiableListIterator<E> implements ListIterator<E> {
private final ListIterator<E> delegate;
public UnmodifiableListIterator(ListIterator<E> delegate) {
this.delegate = delegate;
}
// 只读方法直接委托
public boolean hasNext() { return delegate.hasNext(); }
public E next() { return delegate.next(); }
// ...其他查询方法
// 修改方法抛出异常
public void add(E e) { throw new UnsupportedOperationException(); }
public void remove() { throw new UnsupportedOperationException(); }
public void set(E e) { throw new UnsupportedOperationException(); }
}
虽然Java 8的Stream API提供了强大的集合操作能力,但在需要双向遍历或精确位置控制的场景下,ListIterator仍然是更好的选择。不过两者可以结合使用:
java复制List<String> list = ...;
// 先用Stream处理过滤
List<String> filtered = list.stream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// 再用ListIterator精细处理
ListIterator<String> it = filtered.listIterator();
while(it.hasNext()) {
String s = it.next();
if(s.startsWith("A")) {
it.set(s.toLowerCase());
}
}
对于性能敏感的应用程序,可以包装ListIterator添加监控功能:
java复制public class MonitoredListIterator<E> implements ListIterator<E> {
private final ListIterator<E> delegate;
private int nextCount = 0;
private int prevCount = 0;
// 实现所有方法,记录调用统计
public void printStats() {
System.out.println("next() calls: " + nextCount);
System.out.println("previous() calls: " + prevCount);
}
}
这种模式在调试复杂算法时特别有用,可以帮助我们理解迭代器的实际使用情况。