1. ListIterator基础:为什么需要它?
在Java集合操作中,我们经常会遇到这样的场景:遍历集合的同时需要修改元素内容。初学者可能会直接使用for循环配合get/set方法,或者尝试用增强for循环修改元素,结果往往遭遇ConcurrentModificationException异常。这正是ListIterator存在的意义——它提供了在遍历过程中安全修改集合的能力。
与普通Iterator相比,ListIterator有三个关键增强特性:
- 双向遍历能力(hasPrevious()/previous())
- 遍历时修改元素(set()方法)
- 遍历时添加元素(add()方法)
重要提示:ListIterator仅存在于List接口的实现类中(如ArrayList、LinkedList),Set和Map没有这种迭代器。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心API解析与使用场景
2.1 获取ListIterator实例
所有List实现类都提供了listIterator()方法:
java复制List<String> languages = new ArrayList<>(Arrays.asList("Java", "Python", "C++"));
ListIterator<String> iterator = languages.listIterator();
可以指定起始位置:
java复制// 从索引1(Python)开始迭代
ListIterator<String> iterator = languages.listIterator(1);
2.2 关键方法详解
| 方法 | 返回值 | 说明 |
|---|---|---|
| hasNext() | boolean | 是否还有下一个元素 |
| next() | E | 返回下一个元素并移动指针 |
| hasPrevious() | boolean | 是否还有上一个元素 |
| previous() | E | 返回上一个元素并移动指针 |
| nextIndex() | int | 返回下一个元素的索引 |
| previousIndex() | int | 返回上一个元素的索引 |
| set(E e) | void | 替换最后一次访问的元素 |
| add(E e) | void | 在当前位置插入元素 |
3. 修改集合的四种典型场景
3.1 基本元素替换
最常见的用法是在遍历时修改元素值:
java复制List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
ListIterator<Integer> it = numbers.listIterator();
while(it.hasNext()) {
int num = it.next();
if(num % 2 == 0) {
it.set(num * 10); // 将偶数乘以10
}
}
// 结果:[1, 20, 3, 40]
踩坑提醒:set()必须紧跟在next()或previous()之后调用,否则会抛出IllegalStateException
3.2 条件性插入新元素
在特定位置插入元素:
java复制List<String> fruits = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
ListIterator<String> it = fruits.listIterator();
while(it.hasNext()) {
String fruit = it.next();
if(fruit.startsWith("B")) {
it.add("Blueberry"); // 在"Banana"后插入
}
}
// 结果:[Apple, Banana, Blueberry, Cherry]
3.3 双向遍历与修改
ListIterator支持前后移动的特性:
java复制List<Character> chars = new ArrayList<>(Arrays.asList('a', 'b', 'c'));
ListIterator<Character> it = chars.listIterator();
it.next(); // a
it.next(); // b
it.set('B');
it.previous(); // 回到B
it.previous(); // 回到a
it.set('A');
// 结果:[A, B, c]
3.4 复杂条件修改
结合多个条件进行复杂操作:
java复制List<Employee> employees = getEmployeeList();
ListIterator<Employee> it = employees.listIterator();
while(it.hasNext()) {
Employee emp = it.next();
if(emp.getAge() > 60) {
it.set(new RetiredEmployee(emp)); // 替换为退休员工对象
} else if(emp.getSalary() < 5000) {
emp.setSalary(5000); // 直接修改对象属性
it.set(emp);
}
}
4. 底层原理与线程安全
4.1 实现机制分析
以ArrayList为例,ListIterator内部维护着:
- 当前游标位置(cursor)
- 最后访问的索引(lastRet)
- 预期修改次数(expectedModCount)
当调用set()或add()时:
- 检查modCount是否等于expectedModCount
- 执行底层数组的修改操作
- 更新lastRet索引
4.2 并发修改异常防护
常见的ConcurrentModificationException触发场景:
java复制List<String> list = new ArrayList<>(...);
Iterator<String> it = list.iterator();
list.add("new element"); // 结构性修改
it.next(); // 抛出异常
ListIterator通过以下机制避免这个问题:
- 初始化时记录modCount到expectedModCount
- 每次操作前检查两者是否一致
- 自身的add/set方法会更新modCount和expectedModCount
4.3 线程安全注意事项
虽然ListIterator有修改检查机制,但在多线程环境下:
java复制// 线程不安全的示例
List<String> sharedList = Collections.synchronizedList(new ArrayList<>());
ListIterator<String> it = sharedList.listIterator();
// 线程1:
while(it.hasNext()) {
String item = it.next();
// 线程2可能在此处修改集合
it.set(item.toUpperCase()); // 可能失败
}
解决方案:
- 使用显式同步锁
- 改用CopyOnWriteArrayList
- 先创建集合的快照再迭代
5. 性能对比与最佳实践
5.1 不同实现类的性能差异
| 操作 | ArrayList | LinkedList |
|---|---|---|
| next() | O(1) | O(1) |
| previous() | O(1) | O(1) |
| set() | O(1) | O(1) |
| add() | O(n) | O(1) |
5.2 与普通for循环对比
java复制// 传统for循环
for(int i=0; i<list.size(); i++) {
if(condition(list.get(i))) {
list.set(i, newValue);
}
}
// ListIterator版本
ListIterator<T> it = list.listIterator();
while(it.hasNext()) {
T item = it.next();
if(condition(item)) {
it.set(newValue);
}
}
优势比较:
- LinkedList场景下,ListIterator的set()效率更高
- 代码意图更明确
- 支持双向遍历
5.3 实用技巧与陷阱
- 批量修改优化:
java复制// 低效做法:多次调用set()
for(int i=0; i<list.size(); i++) {
list.set(i, transform(list.get(i)));
}
// 高效做法:使用ListIterator
ListIterator<T> it = list.listIterator();
while(it.hasNext()) {
it.set(transform(it.next()));
}
- 边界条件处理:
java复制ListIterator<String> it = list.listIterator(list.size()); // 从末尾开始
while(it.hasPrevious()) {
String s = it.previous();
if(s.length() > 10) {
it.set(s.substring(0, 10));
}
}
- 常见错误示例:
java复制ListIterator<String> it = list.listIterator();
it.next();
it.add("new"); // 正确
it.set("modified"); // 错误!add()后lastRet被重置为-1
// 正确顺序:
it.next();
it.set("modified"); // 先set
it.add("new"); // 后add
6. 实际应用案例
6.1 文本处理示例
处理文档中的敏感词:
java复制List<String> documentLines = Files.readAllLines(Paths.get("document.txt"));
ListIterator<String> it = documentLines.listIterator();
Set<String> sensitiveWords = loadSensitiveWords();
while(it.hasNext()) {
String line = it.next();
for(String word : sensitiveWords) {
if(line.contains(word)) {
line = line.replace(word, "***");
it.set(line);
}
}
}
6.2 游戏开发中的应用
游戏实体状态更新:
java复制List<GameEntity> entities = getGameEntities();
ListIterator<GameEntity> it = entities.listIterator();
while(it.hasNext()) {
GameEntity entity = it.next();
if(entity.isExpired()) {
it.remove();
} else {
entity.update();
if(entity.shouldSplit()) {
it.add(entity.createClone());
}
}
}
6.3 数据库批量操作
模拟事务性更新:
java复制List<Record> records = fetchRecordsFromDB();
ListIterator<Record> it = records.listIterator();
boolean hasError = false;
while(it.hasNext() && !hasError) {
Record record = it.next();
try {
Record updated = processRecord(record);
it.set(updated);
} catch(Exception e) {
hasError = true;
// 回滚已修改的记录
while(it.hasPrevious()) {
Record prev = it.previous();
it.set(rollbackRecord(prev));
}
}
}
if(!hasError) {
batchUpdateToDB(records);
}
在Java集合框架中,ListIterator是一个强大但经常被低估的工具。它不仅解决了遍历时修改集合的需求,还提供了双向访问的能力。经过多年的Java开发实践,我发现合理使用ListIterator可以:
- 使代码更简洁清晰
- 避免常见的并发修改异常
- 在某些场景下显著提升性能
特别是在处理大型列表时,相比传统的索引访问方式,ListIterator通常能提供更好的性能表现。不过也要注意它的局限性——比如不支持并发修改检测之外的其他线程安全机制。
