1. 引言
在 Java 集合框架中,Iterator是遍历集合元素的标准接口,但它有一个明显的局限——只能单向遍历。当我们需要在遍历过程中反向移动、获取上一个元素或在遍历时安全地修改列表时,Iterator就显得力不从心了。
ListIterator接口正是为解决这些问题而生。它是Iterator的子接口,专门为List系列集合(如ArrayList、LinkedList)提供双向遍历和列表修改的能力。本文将深入剖析ListIterator的源码设计、核心方法、使用场景与注意事项,帮助你彻底掌握这一利器。
2. ListIterator 接口概览
2.1 接口定义
ListIterator位于java.util包中,其接口定义如下:
publicinterfaceListIterator<E>extendsIterator<E>{// 查询操作booleanhasNext();Enext();booleanhasPrevious();Eprevious();intnextIndex();intpreviousIndex();// 修改操作voidremove();voidset(Ee);voidadd(Ee);}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()向前移动。
List<String>list=newArrayList<>(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 A3.2 索引访问方法
nextIndex()返回下一次调用next()将返回的元素的索引;previousIndex()返回下一次调用previous()将返回的元素的索引。这两个方法在需要定位操作时非常有用。
List<String>list=newArrayList<>(Arrays.asList("A","B","C"));ListIterator<String>it=list.listIterator();System.out.println("nextIndex: "+it.nextIndex());// 0System.out.println("previousIndex: "+it.previousIndex());// -1it.next();// 移动到 "A"System.out.println("nextIndex: "+it.nextIndex());// 1System.out.println("previousIndex: "+it.previousIndex());// 03.3 修改方法:set 与 add
set(E e)用于替换最近一次由next()或previous()返回的元素;add(E e)用于在当前迭代位置插入新元素。
List<String>list=newArrayList<>(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的方法,各有适用场景:
List<String>list=newArrayList<>(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 反向遍历列表
List<Integer>numbers=newArrayList<>(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 15.2 遍历时安全地替换元素
List<Integer>numbers=newArrayList<>(Arrays.asList(1,2,3,4,5));ListIterator<Integer>it=numbers.listIterator();while(it.hasNext()){intnum=it.next();if(num%2==0){it.set(num*10);// 将偶数放大 10 倍}}System.out.println(numbers);// [1, 20, 3, 40, 5]5.3 在指定位置插入元素
List<String>list=newArrayList<>(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。
List<String>list=newArrayList<>(Arrays.asList("A","B","C"));ListIterator<String>it=list.listIterator();while(it.hasNext()){Strings=it.next();if(s.equals("B")){list.remove("B");// 错误!应使用 it.remove()}}// 抛出 ConcurrentModificationException6.2 set 与 remove 的调用限制
set和remove方法都要求最近一次调用必须是next()或previous()。如果先调用了add(),再调用set()或remove(),会抛出IllegalStateException。
List<String>list=newArrayList<>(Arrays.asList("A","B"));ListIterator<String>it=list.listIterator();it.next();// 合法it.add("X");// 插入后,迭代器状态改变it.set("Y");// 抛出 IllegalStateException!6.3 add 方法对迭代器位置的影响
调用add()后,新元素被插入到迭代器当前位置之前,且next()返回的元素不变,但previous()会返回新插入的元素。
List<String>list=newArrayList<>(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接口。核心字段包括:
privateclassListItrextendsItrimplementsListIterator<E>{// 游标位置,即下一个 next() 返回元素的索引privateintcursor;// 最近一次返回元素的索引,-1 表示没有privateintlastRet=-1;// 期望的修改次数,用于快速失败检测privateintexpectedModCount=modCount;}add方法的实现体现了其位置语义:
publicvoidadd(Ee){checkForComodification();try{inti=cursor;ArrayList.this.add(i,e);// 在游标位置插入cursor=i+1;// 游标后移lastRet=-1;// 重置 lastRetexpectedModCount=modCount;}catch(IndexOutOfBoundsExceptionex){thrownewConcurrentModificationException();}}8. 总结
ListIterator是 Java 集合框架中一个功能强大的接口,它弥补了Iterator单向遍历的不足,提供了双向遍历、索引访问和列表修改三大核心能力。在实际开发中,当你需要反向遍历列表、在遍历时替换或插入元素时,ListIterator是最佳选择。
掌握ListIterator的关键在于理解其游标语义:next()与previous()如何移动游标、set()与remove()的调用前提、以及add()对游标位置的影响。希望本文能帮助你更好地理解和使用这一接口,写出更优雅、更高效的 Java 代码。