Java集合框架中的Iterator接口使用方法

Iterator使用需遵守契约:必须先hasNext()再next(),remove()仅限next()后立即调用;foreach本质是Iterator,禁止直接修改集合;ListIterator支持双向遍历但仅适用于List。

Iterator.next() 调用前必须先调用 hasNext()

直接调用 next() 而不检查 hasNext(),几乎必然抛出 NoSuchElementException。这不是设计缺陷,而是 Iterator 的契约:它不负责“预判”是否还有元素,只负责“取下一个”。

  • 正确写法永远是:while (iterator.hasNext()) { Object o = iterator.next(); ... }
  • 循环中多次调用 next()(比如想跳过一个元素)会导致漏读——每次 next() 都推进内部指针
  • 对空集合调用 iterator() 返回有效对象,但首次 hasNext() 就返回 false,不会报错

remove() 只能在 next() 之后立即调用一次

Iterator.remove() 是唯一安全的集合遍历中删除元素的方式,但它有严格时序约束:必须在调用 next() 之后、且在下一次 next() 之前调用;重复调用或在 hasNext() 后直接调用会触发 IllegalStateException

  • 错误示范:
    iterator.remove(); // IllegalStateException:还没 next()
  • 错误示范:
    iterator.next(); iterator.next(); iterator.remove(); // IllegalStateException:上个 next 已被覆盖
  • 正确场景:仅用于“处理完当前元素后删掉它”,例如过滤日志中已归档的记录

foreach 循环底层就是 Iterator,但禁止在其中修改集合

Java 的 for (String s : list) 本质是语法糖,编译后等价于显式使用 Iterator。这意味着:在 foreach 中调用 list.remove()list.add() 会立刻触发 ConcurrentModificationException——即使单线程也如此。

  • 原因:集合内部的 modCount 与迭代器持有的 expectedModCount 不匹配
  • 替代方案只有两个:iterator.remove()(安全删除),或收集待删元素再批量操作(如 removeAll(toRemove)
  • 注意:CopyOnWriteArrayList 是例外,它的迭代器不检查并发修改,但代价是每次写都复制数组,不适合高频写场景

ListIterator 支持双向遍历和索引定位,但仅限 List

ListIteratorIterator 的子接口,只由 ArrayListLinkedListList 实现提供。它能向前/向后移动、获取当前位置索引、甚至设置当前元素值。

  • listIterator() 从头开始;listIterator(int index) 从指定索引开始(索引可等于 list.size(),此时 hasPrevious()truehasNext()false
  • nextIndex()previousIndex() 返回的是“下一次 next()previous() 将访问的索引”,不是当前元素索引
  • 不能用于 SetMap —— 它们没有定义顺序索引的概念
Iterator 的核心就两条:指针推进不可逆,修改必须受控。很多人卡在 ConcurrentModificationException 上,其实问题不在“并发”,而在于没意识到 foreach 和显式 Iterator 共享同一套检测机制。