Java集合学习之forEach()遍历方法的底层原理
forEach()时Java集合中十分便捷的一个函数,但是很多人在使用很久后只知道其如何使用,但是并不知道其底层的实现原理时什么,Java是一门开源的语言,因此我们可以通过查阅底层的实现源代码来一窥其真容,这对我们未来集合的学习将起到至关重要的作用。
集合的 forEach()
方法是遍历元素的便捷方式,其底层原理基于 函数式接口 和 迭代器模式。
一.forEach的使用
我们先给出一段forEach() 的使用代码:
import java.util.*;public class ForEachExample {public static void main(String[] args) {// 1. List 遍历List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");System.out.println("=== List 遍历 ===");fruits.forEach(fruit -> System.out.println("水果: " + fruit));// 2. Set 遍历Set<Integer> numbers = new HashSet<>(Arrays.asList(10, 20, 30, 40));System.out.println("\n=== Set 遍历 ===");numbers.forEach(number -> {int squared = number * number;System.out.println(number + "的平方: " + squared);});// 3. Map 遍历 (使用 BiConsumer)Map<String, Integer> priceMap = new HashMap<>();priceMap.put("iPhone", 6999);priceMap.put("iPad", 3299);priceMap.put("MacBook", 12999);}
}
输出结果如下:
=== List 遍历 ===
水果: Apple
水果: Banana
水果: Cherry=== Set 遍历 ===
20的平方: 400
40的平方: 1600
10的平方: 100
30的平方: 900=== Map 遍历 ===
MacBook 价格: ¥12999
iPhone 价格: ¥6999
iPad 价格: ¥3299
二.查看forEach源码
将鼠标放置在idle的forEach()方法上,点击ctrl+B,就会跳转在forEach()方法的源码中,如下图所示:
三.源码分析
我们将使用的方法的语句与定义forEach()方法的语句对比来看,定义forEach()方法的参数中有Consumer<? super T> action,那么这个就是我们对应的lambda表达式,那么其中的Consumer是什么?
答案就是一个接口,接口的内容如下所示:
所以我们就是以函数式编程的方法完成了Consumer接口中的accept方法,然后在由forEach()方法去调用这个方法通过增强for的形式完成我们要执行的内容。
关键角色:Consumer
函数式接口
Consumer<T>
是一个函数式接口,定义了accept(T t)
方法。使用 Lambda 或方法引用时,编译器会生成
Consumer
的实现:
list.forEach(element -> System.out.println(element));
// 等价于
list.forEach(new Consumer<>() {@Overridepublic void accept(String element) {System.out.println(element);}
});
以上的内容都是基于List完成,部分集合类重写了
forEach()
以提升性能
使用`forEach()`方法与使用显式的迭代器(Iterator)或增强型for循环在性能上几乎没有差别,因为它们的底层实现都是迭代器。但是,在某些特定的集合实现中,如果覆盖了`forEach()`方法并进行了优化,可能会有微小差异。不过,在大多数情况下,这种差异可以忽略不计。
ArrayList:直接遍历数组,避免迭代器开销:
public void forEach(Consumer<? super E> action) {final E[] elementData = this.elementData;for (int i = 0; i < size; i++) {action.accept(elementData[i]);}
}
HashMap:遍历桶数组和链表/红黑树:
public void forEach(BiConsumer<? super K, ? super V> action) {Node<K,V>[] tab = table;for (Node<K,V> node : tab) {while (node != null) {action.accept(node.key, node.value);node = node.next;}}
}
`forEach()`方法接受一个`Consumer`(对于Map是`BiConsumer`),所以我们可以使用Lambda表达式来传递行为。这使得代码更加简洁和易读。
四.与 Stream API 的关系
forEach()
也可用于 Stream
:
list.stream().forEach(System.out::println);
区别:Stream.forEach()
不保证顺序(除非调用 forEachOrdered
),而集合的 forEach()
按迭代顺序执行。
五.总结
关键点 | 说明 |
---|---|
底层机制 | 基于迭代器模式遍历元素 |
函数式接口 | 依赖 Consumer 接收操作逻辑 |
性能优化 | ArrayList /HashMap 等重写方法避免迭代器开销 |
并发修改 | 直接修改集合会抛出 ConcurrentModificationException |
与 Stream 区别 | 集合的 forEach() 保证顺序;Stream.forEach() 不保证(并行流时明显) |