在Java集合框架中,ListIterator是一个容易被忽视但极其重要的接口。作为Iterator的增强版,它不仅支持双向遍历,还能在迭代过程中执行元素修改操作。我在实际开发中多次遇到需要反向遍历列表或边遍历边修改的场景,这时候ListIterator就成为了救星。
这个接口特别适合处理以下场景:
与普通Iterator相比,ListIterator提供了更丰富的操作方法,让我们能够更灵活地处理列表数据。接下来我将从底层实现到实际应用,全面剖析这个强大的工具。
ListIterator接口定义了9个核心方法,比Iterator多了6个:
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);
}
每个方法都有其特定的使用场景和注意事项:
双向遍历方法:
hasNext()/next():正向遍历标准组合hasPrevious()/previous():反向遍历利器定位方法:
nextIndex():返回下一个元素的索引previousIndex():返回前一个元素的索引修改方法:
set(E e):替换最后一次访问的元素add(E e):在当前位置插入元素remove():删除最后一次访问的元素重要提示:修改操作必须遵循特定顺序。必须在next()或previous()之后才能调用set()/remove(),否则会抛出IllegalStateException。
以ArrayList中的ListItr实现为例,关键字段包括:
lastReturned:记录最后一次返回的节点cursor:下一个元素的索引expectedModCount:用于快速失败机制java复制private class ListItr implements ListIterator<E> {
private int cursor; // 下一个元素的索引
private int lastRet = -1; // 最后返回的索引
private int expectedModCount = modCount;
// 构造方法和其他方法实现...
}
快速失败机制是集合框架中常见的并发修改检测方式。当迭代过程中发现modCount != expectedModCount时,立即抛出ConcurrentModificationException。这种设计在单线程环境下能快速发现问题,但在多线程场景下仍需配合外部同步机制。
假设我们需要实现一个文本编辑器的历史记录功能:
java复制List<String> history = new ArrayList<>();
ListIterator<String> it = history.listIterator();
// 添加操作记录
it.add("输入'Hello'");
it.add("删除'llo'");
it.add("输入'p'");
// 正向遍历
while(it.hasNext()) {
System.out.println("操作:" + it.next());
}
// 反向遍历(撤销功能)
while(it.hasPrevious()) {
String action = it.previous();
if(shouldUndo(action)) {
System.out.println("撤销:" + action);
it.remove();
}
}
在处理数据清洗时,我们经常需要边遍历边修改:
java复制List<String> data = Arrays.asList("A", "B", "C", "D");
ListIterator<String> it = data.listIterator();
while(it.hasNext()) {
String item = it.next();
if(item.equals("B")) {
it.set("B-New"); // 替换当前元素
}
if(item.equals("C")) {
it.add("C-Add"); // 在C后插入新元素
}
}
java复制List<String> list = new ArrayList<>(Arrays.asList("A","B","C","D"));
ListIterator<String> it = list.listIterator(2); // 从索引2开始
System.out.println("前一个索引:" + it.previousIndex()); // 1
System.out.println("下一个索引:" + it.nextIndex()); // 2
| 操作 | ArrayList | LinkedList |
|---|---|---|
| next() | O(1) | O(1) |
| previous() | O(1) | O(1) |
| add() | O(n) | O(1) |
| remove() | O(n) | O(1) |
| set() | O(1) | O(1) |
从表中可以看出,LinkedList在修改操作上具有明显优势,而ArrayList在随机访问时更快。选择哪种实现应根据具体使用场景决定。
并发修改问题:
操作顺序错误:
java复制ListIterator<String> it = list.listIterator();
it.set("new"); // 错误!必须先调用next()或previous()
索引越界:
修改后游标位置:
利用ListIterator可以高效实现LRU(最近最少使用)缓存:
java复制public class LRUCache<K,V> {
private final int capacity;
private final Map<K,V> map;
private final LinkedList<K> list;
public V get(K key) {
if(map.containsKey(key)) {
ListIterator<K> it = list.listIterator();
while(it.hasNext()) {
if(it.next().equals(key)) {
it.remove();
list.addFirst(key);
break;
}
}
return map.get(key);
}
return null;
}
}
实现简单的文本差异比较工具:
java复制public static List<String> diff(List<String> oldList, List<String> newList) {
List<String> changes = new ArrayList<>();
ListIterator<String> oldIt = oldList.listIterator();
ListIterator<String> newIt = newList.listIterator();
while(oldIt.hasNext() && newIt.hasNext()) {
String oldLine = oldIt.next();
String newLine = newIt.next();
if(!oldLine.equals(newLine)) {
changes.add("修改: " + oldLine + " -> " + newLine);
}
}
// 处理剩余行...
return changes;
}
高效合并两个已排序列表:
java复制public static <T extends Comparable<T>> List<T> mergeSorted(
List<T> list1, List<T> list2) {
List<T> result = new ArrayList<>();
ListIterator<T> it1 = list1.listIterator();
ListIterator<T> it2 = list2.listIterator();
T item1 = it1.hasNext() ? it1.next() : null;
T item2 = it2.hasNext() ? it2.next() : null;
while(item1 != null && item2 != null) {
if(item1.compareTo(item2) <= 0) {
result.add(item1);
item1 = it1.hasNext() ? it1.next() : null;
} else {
result.add(item2);
item2 = it2.hasNext() ? it2.next() : null;
}
}
// 添加剩余元素...
return result;
}
ListIterator是迭代器模式的经典实现,提供了比基本Iterator更丰富的遍历能力。在设计自定义集合类时,可以考虑实现ListIterator接口来提供更多功能。
结合备忘录模式实现可回退的操作序列:
java复制public class TextEditor {
private final List<String> content = new ArrayList<>();
private final ListIterator<String> iterator = content.listIterator();
public void type(String text) {
iterator.add(text);
}
public void undo() {
if(iterator.hasPrevious()) {
iterator.previous();
iterator.remove();
}
}
}
虽然Java 8引入了Stream API,但ListIterator仍有其独特优势:
| 特性 | ListIterator | Stream API |
|---|---|---|
| 双向遍历 | 支持 | 不支持 |
| 遍历中修改 | 支持 | 不支持 |
| 获取当前位置 | 支持 | 不支持 |
| 并行处理 | 不支持 | 支持 |
| 函数式操作 | 有限支持 | 全面支持 |
在实际项目中,我通常这样选择:
java复制List<String> syncList = Collections.synchronizedList(new ArrayList<>());
// 使用时需要手动同步
synchronized(syncList) {
ListIterator<String> it = syncList.listIterator();
while(it.hasNext()) {
// 操作...
}
}
适合读多写少的场景:
java复制List<String> cowList = new CopyOnWriteArrayList<>();
// 迭代器创建时会复制底层数组
ListIterator<String> it = cowList.listIterator();
// 迭代过程中其他线程的修改不会影响当前迭代
在多线程环境下,我通常采用以下策略之一:
当我们需要特殊遍历逻辑时,可以自定义ListIterator:
java复制public class SkipListIterator<E> implements ListIterator<E> {
private final ListIterator<E> delegate;
private final int skipStep;
public SkipListIterator(List<E> list, int skip) {
this.delegate = list.listIterator();
this.skipStep = skip;
}
@Override
public E next() {
E item = delegate.next();
for(int i=0; i<skipStep && delegate.hasNext(); i++) {
delegate.next();
}
return item;
}
// 其他方法实现...
}
与C++的std::list::iterator相比,Java的ListIterator:
与Python的list迭代器相比:
在开发文档编辑器时,我们重度依赖ListIterator实现以下功能:
遇到的典型问题及解决方案:
问题1:在大文档操作时性能下降
问题2:并发修改导致异常
问题3:复杂操作顺序导致状态混乱
一个实用的工具方法:
java复制public static <T> void replaceAll(List<T> list,
Predicate<T> predicate, UnaryOperator<T> operator) {
ListIterator<T> it = list.listIterator();
while(it.hasNext()) {
T item = it.next();
if(predicate.test(item)) {
it.set(operator.apply(item));
}
}
}
这个方法比Java 8的replaceAll更灵活,可以基于条件进行替换。