Java迭代器进阶用法包括使用Iterator安全删除元素、对比ListIterator选择合适遍历器、自定义迭代器实现业务逻辑、以及在并发场景下避免Fail-Fast机制,正确使用迭代器能提升代码健壮性与性能。
Java迭代器遍历时删除元素的正确做法
遍历集合时调用集合自身的remove方法,会在下一次迭代时抛出ConcurrentModificationException,这是很多开发者初学时踩过的坑,迭代器提供的remove方法,是唯一能在遍历过程中安全删除当前元素的方式。
为什么不能用集合的remove方法
集合持有内部计数器modCount,每次结构性修改都会递增,迭代器在创建时记录这个值,并在每次next调用时检查,如果发现集合的modCount与迭代器记录的不一致,立即抛出异常。业内专家指出,fail-fast机制是集合框架在检测并发修改时的一种保护策略,但代价是牺牲了遍历时的修改灵活性。
迭代器remove方法的正确使用姿势
- 调用next拿到当前元素后,才能调用remove。
- 每次调用remove后,必须再次调用next才能继续删除下一个元素。
- 不能连续两次调用remove,否则会抛IllegalStateException。
实操步骤:
- 获取迭代器:
Iterator<String> it = list.iterator(); - 循环中先调用
it.next()获取元素。 - 判断条件满足时,调用
it.remove()。 - 继续循环,重复步骤2-3。
使用迭代器删除多个元素时的注意事项
如果需要遍历时删除多个元素,但删除条件不依赖当前元素,建议先收集要删除的元素,遍历结束后统一删除。行业共识认为,遍历时删除元素必须使用迭代器自己的remove方法,但删除后集合结构变化会影响后续迭代,应尽量保持删除动作在同一个next之后执行。
Java迭代器与ListIterator的区别分析
ListIterator是Iterator的子接口,专门用于List集合,两者在功能和使用场景上有明显差异。
| 对比项 | Iterator | ListIterator |
|---|---|---|
| 遍历方向 | 单向(从前到后) | 双向(hasPrevious/previous) |
| 添加元素 | 不支持 | 支持add方法 |
| 修改元素 | 不支持 | 支持set方法 |
| 获取索引 | 不支持 | 支持nextIndex/previousIndex |
| 适用范围 | 所有Collection | 仅List及其子类 |
什么时候选择ListIterator
- 需要反向遍历集合时,例如从列表尾部开始查找。
- 遍历过程中需要插入新元素或修改当前元素时,ListIterator的add和set方法能保证正确性。
- 需要获取当前元素在列表中的索引位置。
迭代器与for-each循环的性能对比
for-each底层本质上也是使用迭代器,性能差异微乎其微,但for-each无法调用remove方法,也无法在遍历时修改集合结构,如果只需要遍历读取,for-each语法更简洁;如果需要删除或修改,必须显式使用迭代器。
并发环境下的迭代器使用与线程安全
多线程环境中使用普通集合的迭代器,会触发fail-fast机制,导致不确定的行为,Java提供了并发集合类,其迭代器采用fail-safe机制,允许在遍历时集合被修改。
fail-fast与fail-safe机制
- fail-fast:检测到迭代过程中集合被修改,立即抛出ConcurrentModificationException,常见于ArrayList、HashMap等非线程安全集合。
- fail-safe:迭代器基于集合的快照或CopyOnWrite机制,在遍历时修改集合不会影响迭代过程,也不会抛出异常,常见于ConcurrentHashMap、CopyOnWriteArrayList。
如何安全地在多线程中使用迭代器
- 使用同步代码块锁住整个集合,但会降低并发性。
- 改用并发集合类,如CopyOnWriteArrayList或ConcurrentHashMap,它们的迭代器天然支持并发遍历。
- 遍历前对集合做一次快照,例如
new ArrayList<>(originalList),但快照会占用额外内存。
迭代器线程安全吗?常见误区
迭代器本身不是线程安全的,即使集合是线程安全的,如果多个线程共享同一个迭代器,仍需外部同步。据统计,相当一部分并发异常源于多线程共用迭代器而未加锁,正确的做法是每个线程获取自己的迭代器实例,或者使用并发集合的迭代器。
自定义迭代器实现与业务集成
迭代器接口后,可以定义自己的遍历逻辑,实现惰性加载或特定数据结构的遍历。
实现Iterator接口的步骤
- 实现
hasNext()方法,判断是否还有下一个元素。 - 实现
next()方法,返回当前元素,并将指针前移。 - 可选实现
remove()方法,如果不需要支持删除,直接抛出UnsupportedOperationException。
代码示例(伪代码,专注于步骤):
public class MyIterator<T> implements Iterator<T> {
private T[] data;
private int cursor = 0;
public boolean hasNext() { return cursor < data.length; }
public T next() { return data[cursor++]; }
public void remove() { throw new UnsupportedOperationException(); }
}
自定义迭代器实战:遍历文件行
场景:逐行读取大文件,使用迭代器模式实现懒加载,避免一次性加载全部内容到内存,迭代器内部持有一个BufferedReader,在next时读取一行,在hasNext时判断是否还有下一行。
操作路径:
- 构造时传入文件路径,打开BufferedReader。
- hasNext尝试读取下一行,若不为null则缓存,返回true。
- next返回缓存行,并继续预读下一行。
- 迭代完成后关闭文件流。
迭代器模式在业务中的价值
将遍历逻辑与集合实现分离,客户端只需面对迭代器接口,无需关心底层数据结构,对于复杂遍历(如树形结构、分页数据),自定义迭代器能简化调用方代码,提高可维护性。
Java迭代器性能优化核心要点
熟练使用迭代器后,优化其性能能进一步提升整体应用效率。
避免在迭代中调用集合的size()方法
每次调用size(),对于LinkedList可能需要遍历计算,造成性能损耗。如果需要预判循环次数,应在迭代前用变量保存size值。
使用迭代器遍历ArrayList与LinkedList的性能差异
- ArrayList基于数组,使用索引遍历(get方法)效率高,迭代器遍历也很快,但迭代器多了一层对象创建和指针移动。
- LinkedList基于链表,使用索引遍历效率极低(O(n)),而迭代器遍历直接通过节点指针移动,时间复杂度O(n),是LinkedList遍历的首选。
迭代器内部结构对性能的影响
迭代器会记录当前节点位置,对于LinkedList,每次next只是移动指针到下一个节点,不像get(index)需要从头遍历。遍历LinkedList时,永远使用迭代器或for-each,避免使用索引。
Java迭代器进阶用法Q&A
迭代器遍历时删除元素为什么必须用iterator.remove?
因为集合的remove会修改集合的modCount,导致迭代器在下次next时检测到expectedModCount不一致,抛出ConcurrentModificationException,iterator.remove在删除元素后同步更新期望的modCount,保证迭代正常进行,这是迭代器设计时约定的安全操作。
迭代器与ListIterator哪个性能更好?
在单纯的正向遍历读取上,两者性能基本一致,但ListIterator支持更多操作,如果需要在遍历时添加或修改元素,使用ListIterator可以避免额外的集合操作开销,性能差异主要体现在功能扩展上,而非遍历速度,选择时优先根据功能需求,而不是性能。
如何自定义迭代器并保证线程安全?
参考CopyOnWriteArrayList的实现:在迭代器创建时保存当前数组的快照引用,后续遍历全部基于快照,即使原集合被修改,迭代器也不受影响,对于需要线程安全的自定义迭代器,可以采用快照策略,或者使用锁机制保证hasNext和next的原子性,注意,快照方式会占用内存,适用于读多写少的场景。
掌握迭代器进阶用法,能让你在Java集合处理中避免常见陷阱,写出更高效、更健壮的代码。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/547560.html




