Menu

Java 遍历集合:for-each、Iterator 与 forEach

遍历 Java 集合的几种方式:for-each 循环、Iterator、索引循环和 forEach 方法,以及如何在遍历时安全地删除元素。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

同一件事,好几种工具

你已经把一些数据放进了 ArrayList、HashSet 或 HashMap,现在想访问其中的每个元素。Java 提供了几种做法,选哪一种取决于:你是否需要索引,是否需要在循环中途删除元素,以及你更喜欢方法式还是循环式的语法。

好消息是:每个 Collection(列表、集合、队列)都支持同样的增强 for 循环,学会它之后,你就能用同一种方式遍历所有集合。

冒号可以读作“在……之中”:“对 langs 中的每个 lang”。你完全不碰索引,所以也就不会出错。

for-each 循环

对于单纯的“对每个元素做点事”,增强 for 是默认选择。它读起来清晰,在各种集合类型上的用法完全相同。下面用的是 HashSet,它根本没有索引:

关于 HashSet 要记住一点:它没有确定的顺序,所以元素可能以任意顺序打印出来。不过无论如何,for-each 都会把每个元素恰好访问一次。

需要索引的时候

for-each 给你元素,但不给你它的位置。如果你确实需要索引,比如给行编号,或者查看相邻的元素,就用带 size() 和 get(i) 的计数循环。这适用于 List,因为它是按位置排列的;Set 和 Map 没有索引,所以这种写法不适用于它们。

不要只是出于习惯就用这种写法。如果 i 除了 get(i) 之外没有别的用处,for-each 版本更短,也更不容易出错。

遍历 Map

Map 不是 Collection,所以不能直接用 for-each 遍历它,而是要遍历它的三个视图之一。最常用的是 entrySet(),它会把每个键值对一起交给你:

如果只需要键,就遍历 ages.keySet();如果只需要值,就遍历 ages.values()。键和值都需要时,优先用 entrySet():遍历键再在循环里调用 ages.get(key),会在每一轮无谓地多查找一次。

Iterator

for-each 其实是 Iterator 的语法糖。Iterator 是一个对象,通过 hasNext() 和 next() 一次一个元素地遍历集合。你很少需要手写这种循环,但有一个重要的例外:它是在遍历时删除元素的安全方式。

it.remove() 会删除 next() 最后返回的那个元素,迭代器仍然有效。这是在手动循环中修改集合唯一被认可的方式。

陷阱:ConcurrentModificationException

如果在 for-each 循环中直接对集合本身调用 add 或 remove,就会得到 ConcurrentModificationException:迭代器发现集合在它脚下发生了变化,拒绝继续。这是初学者最常犯的错误之一,和任何运行时异常一样,除非你避免触发它,否则它会中止整个方法。

List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4));
for (int n : nums) {
    if (n % 2 == 0) {
        nums.remove(Integer.valueOf(n));   // throws ConcurrentModificationException
    }
}

解决办法几乎总是 removeIf,它用一行代码表达意图,并替你处理遍历:

removeIf 适用于任何 Collection,所以同样的调用也能清理 HashSet。

forEach 方法

每个集合还有一个 forEach 方法,它接收一个 lambda,并对每个元素运行它。这是比循环更偏函数式、表达式风格的替代写法,适合写简短的单行代码:

注意,Map.forEach 直接接收一个双参数的 lambda (key, value),不需要 entrySet()。做简单的操作时用 forEach;当循环体变长,或者需要用 break 提前退出时(lambda 做不到),就回到普通的 for 循环。

下一篇:方法

你已经把数据装进了集合,并用 Java 提供的各种方式遍历了它们。下一步是把行为封装起来:编写你自己的方法,给一段逻辑起个名字,传入输入,然后反复使用。这就是下一篇的内容。

常见问题

Java 中如何遍历列表?

最简洁的方式是增强 for 循环(for-each):for (String s : list) { ... }。它适用于任何 Collection,比如 ArrayList、HashSet 等。只有在确实需要位置时才用带 get(i) 的索引循环;需要在循环中删除元素时,就用 Iterator。

Java 中如何遍历 HashMap?

Map 不是 Collection,所以要遍历它的某个视图。通常的选择是 for (Map.Entry<K, V> e : map.entrySet()),一次遍历就能同时拿到键(e.getKey())和值(e.getValue())。也可以遍历 map.keySet() 只取键,或遍历 map.values() 只取值。

为什么循环时会出现 ConcurrentModificationException?

因为在 for-each 循环遍历集合的过程中,你调用了集合的 add 或 remove。for-each 循环底层使用 Iterator,它检测到集合的结构发生了变化。解决办法是通过 Iterator 自己的 remove() 方法删除元素,或者不用循环,改用 removeIf(...)。

Coddy 编程语言插图

和 Coddy 一起学编程

开始