搜狐技术产品

Java循环你真的用明白了吗

01

前言

这天,review项目的老代码,发现这么一段代码:

List<Object> paramList = request.getParamList();
paramList.forEach(x -> {... ...}); // 一些简单操作
paramList.forEach(x -> {... ...}); // 又一些简单操作
paramList.forEach(x -> {... ...}); // 双一些简单操作

哎,这就一个集合遍历,为啥要遍历三遍嘞?如果集合数据量比较多,合并为一个遍历,接口性能会不会就能提升一些?

paramList.forEach(x -> {
    ... ...// 一些简单操作
    ... ...// 又一些简单操作
    ... ...// 双一些简单操作
});

送上门的优化,不能不要啊!于是就在本地试一下。结果发现:一次循环遍历处理,果然比三次循环处理提升了不少了(毫秒级别)。

于是兴冲冲修改部署测试环境,却发现性能没啥提升。为什么嘞?本地测试明明提升不小,有意思了,那就去探究一下。

02

本地测试

测试环境

jdk 版本:1.8.0_91(64 位)

测试硬件:Intel i7-1185G7(4 核 8 线程)、16GB DDR4、256GB SSD

测试数据:ArrayList 存储 1000 万条 Integer 数据(元素值 0~9999999)

测试方法:空循环遍历一千万数据,每次只执行单次,代码多次执行取平均值。

测试指标:遍历全部元素的总耗时(单位:毫秒)

测试循环类型

// 1. 普通for循环
for
 (int i = 0; i < list.size(); i++) {}

// 2.增强for循环

for
 (Integer num : list) {}

// 3. list.forEach

list.forEach(num -> {});

// 4. stream.forEach

list.stream().forEach(num -> {});

测试代码

public static void main(String[] args) {
    // 初始化数据

    List<Integer> list = new ArrayList<>();
    for
 (int i = 0; i < 1000_0000; i++) {
        list.add(i);
    }
    long
 s = System.currentTimeMillis();
    ... ... //执行测试空循环遍历
    System.out.println("cost: " + (System.currentTimeMillis() - s));
}

测试结果

循环方式执行耗时(ms)
普通for循环
4
增强for循环
17
list.forEach
48
stream.forEach
51

根据结果得到一个问题:同样是顺序循环遍历为什么差距这么大呢?forEach耗时甚至是普通for循环的10倍以上,接下来我们分析一下。

03

底层分析

1. 普通 for 循环

普通 for 循环是最底层的手动迭代模式,arr[i]直接通过「基地址 + 偏移量」计算元素内存地址。

源码:

public E get(int index) {
    Objects.checkIndex(index, size); // 边界检查
    return
 elementData(index);       // 直接访问底层数组
}
// 底层数组访问(无额外逻辑)

E elementData(int index) {
    return
 (E) elementData[index];
}

字节码:

0: iconst_0                     // 推送 int 常量 0 到操作数栈
1
: istore_1                     // 把栈顶的 0 存入局部变量表索引 1 → 初始化循环变量 i=0
2
: iload_1                      // 加载 i 的值(0)到操作数栈
3
: aload_0                      // 加载局部变量表索引0的值
4
: invokeinterface #15, 1       // 调用list.size()方法(常量池#15对应接口方法, 1 表示方法参数个数)
9
: if_icmpge 18                 // 比较栈中两个 int 值:若 i ≥ list.size(),循环结束;否则继续执行
12
: iinc 1, 1                   // i 自增
15
: goto 2                      // 继续循环
18
: return                      // 返回

普通for循环,代码非常简洁, 无方法调用开销,无额外对象创建、无多余方法调用,仅通过基础语法层面的索引操作完成遍历。

2. 增强for循环

增强 for 循环是迭代器(Iterator)的语法糖,jdk5 引入,核心依赖Iterable接口(所有可遍历集合均实现该接口)。

源码:

// ArrayList的迭代器实现
private
 class Itr implements Iterator<E> {
    int
 cursor;             // 下一个要返回的元素索引
    int
 lastRet = -1;       // 上一个返回的元素索引(用于remove)
    int
 expectedModCount = modCount; // 期望的修改次数(防止并发修改)
    public
 boolean hasNext() {
        return
 cursor != size;  // 索引未到集合大小则有下一个元素
    }
    public
 E next() {
        checkForComodification(); // 校验并发修改
        int
 i = cursor;
        if
 (i >= size)
            throw
 new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if
 (i >= elementData.length)
            throw
 new ConcurrentModificationException();
        cursor = i + 1;  // 移动指针
        return
 (E) elementData[lastRet = i]; // 直接访问数组,返回元素
    }
    // 并发修改校验

    final
 void checkForComodification() {
        if
 (modCount != expectedModCount)
            throw
 new ConcurrentModificationException();
    }
}

字节码:

0: aload_0                      // 加载局部变量表索引0的值(当前对象: List 实现类实例)
1
: invokeinterface #11, 1       // 调用 List 接口的 iterator() 方法(常量池#11对应方法),返回 Iterator 接口实例
6
: astore_1                     // 把栈顶的 Iterator 实例存入局部变量表
7
: aload_1                      // 加载 Iterator 实例到栈
8
: invokeinterface #12, 1       // 调用 Iterator 的 hasNext() 方法(常量池#12对应方法): 返回是否还有下一个元素
13
: ifeq 29                     // 判断栈顶hasNext()的返回值:若为 0:循环结束,返回;否则,继续执行
16
: aload_1                     // 加载局部变量Iterator 实例到栈
17
: invokeinterface #13, 1      // 调用 Iterator 的 next() 方法(常量池#13对应方法): 获取下一个元素,返回 Object 类型
22
: checkcast #14               // Object类型强转Integer (常量池#14对应 Integer.class)
25
: astore_2                    // 把强转后的 Integer 元素存入局部变量表索引2 → 保存当前遍历元素
26
: goto 7                      // 继续循环
29
: return                      // 返回

编译器会自动将增强 for 循环转换为「获取迭代器→hasNext()判断→next()获取元素」的逻辑,无需手动管理索引。

3. list.forEach

List.forEach是 jdk8 引入的函数式遍历,定义在Iterable接口中(List继承自Iterable),核心依赖Consumer函数式接口。

源码:

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final
 int expectedModCount = modCount;
    final
 E[] elementData = (E[]) this.elementData;
    final
 int size = this.size;
    for
 (int i=0; modCount == expectedModCount && i < size; i++) {
        // 调用Consumer处理当前元素

        action.accept(elementData[i]);
    }
    if
 (modCount != expectedModCount) {
        throw
 new ConcurrentModificationException();
    }
}

字节码:

0: aload_0                      // 加载局部变量表索引0的值(List<Integer> 实例)
1
: invokedynamic #16, 0         // 动态绑定 Lambda 表达式(核心指令,jdk 7+ 用于 Lambda/函数式接口绑定):
                                //    - #16:常量池索引,对应「Lambda 引导方法(Bootstrap Method)」和相关元数据(如目标接口、方法签名)

                                //    - 0:参数个数 → 表示 Lambda 未捕获外部变量

                                //    - 目标:生成一个 Consumer<Integer> 接口实例(因 forEach 参数是 Consumer),绑定 Lambda 的核心逻辑(即下面的 lambda$test3$0 方法)

                                //    - 执行后,栈顶压入生成的 Consumer 实例

6
: invokeinterface #17, 2       // 调用 List 接口的 forEach 方法(常量池#17对应方法):
                                //    - 方法签名:List.forEach(Consumer<? super E>) → void(接收 Consumer 接口,遍历集合元素并执行 accept 方法)

                                //    - 参数2个:接口方法调用的参数个数 = 调用者(this,List实例) + 显式参数(Consumer实例)

                                //    - 执行逻辑:List 遍历每个元素,调用 Consumer 的 accept 方法(即 Lambda 逻辑)

11
: return                      // 返回

// 私有静态方法 lambda$test3$0:

private
 static void lambda$test3$0(java.lang.Integer);
    Code:
       0
: return                // 空逻辑方法,直接返回

如上,lambda 表达式会编译为匿名内部类实例,每次循环调用 action.accept(elementData[i])。

4. Stream.forEach

Stream.forEach是 jdk8 引入的流式遍历终端操作,基于「Stream 管道模型」和「惰性求值」机制。核心依赖三个组件:

• Stream:流式操作的载体,封装数据来源(集合 / 数组)和中间操作链;

• Spliterator:分割迭代器,负责数据的分割与遍历(支持并行);

• Sink:数据转发器,连接中间操作(如filter)与终端操作(forEach),传递元素并执行逻辑。其底层逻辑是「管道化处理」:中间操作(如filter)会被封装为Sink链,终端操作forEach触发时,Spliterator遍历数据并通过Sink链传递,最终由Consumer处理元素。

源码:

// Stream.forEach的底层实现(ReferencePipeline.forEach)
public
 void forEach(Consumer<? super P_OUT> action) {
    Objects.requireNonNull(action);
    // 封装终端操作的Sink(最终处理元素)

    Sink<P_OUT> sink = new Sink.ChainedReference<P_OUT, P_OUT>(Sink.cancellationRequested()) {
        @Override

        public
 void accept(P_OUT u) {
            action.accept(u); // 调用用户传入的Consumer
        }
    };
    // 触发遍历:Spliterator遍历数据并通过Sink链传递

    evaluate(sink);
}

// 执行遍历

final
 <S extends Sink<P_OUT>> S evaluate(S sink) {
    return
 isParallel() ? parallelEvaluate(sink) : sequentialEvaluate(sink);
}

// 串行遍历

private
 <S extends Sink<P_OUT>> S sequentialEvaluate(S sink) {
    // 构建Sink链(将中间操作的Sink与终端Sink链接)

    Sink<P_OUT> chainedSink = opWrapSink(StreamOpFlag.NOT_CANCELABLE, sink);
    // Spliterator遍历数据,通过Sink链传递

    spliterator().forEachRemaining(chainedSink);
    // 结束

    chainedSink.end();
    return
 sink;
}

字节码:


0
: aload_0                      // 加载局部变量表索引0的值(List<Integer> 实例)
1
: invokeinterface #18, 1       // 调用 List 接口的 stream() 方法(常量池#18对应方法):
                                //    - 方法签名:List.stream() → Stream<E>(将 List 转为顺序流 Stream<Integer>)

                                //    - 参数个数1:接口方法调用的参数包含调用者 this(无额外显式参数)

                                //    - 执行后,栈顶压入返回的 Stream<Integer> 实例

6
: invokedynamic #19, 0         // 动态绑定 Lambda 表达式(jdk 7+ 新增,用于函数式接口实例化):
                                //    - #19:常量池索引,对应「Lambda 引导方法(Bootstrap Method)」+ 元数据(目标接口 Consumer、方法签名)

                                //    - 0:参数个数 → 表示 Lambda 未捕获任何外部变量(无状态 Lambda)

                                //    - 目标:生成 Consumer<Integer> 接口实例,绑定 Lambda 核心逻辑(即下面的 lambda$test4$1 方法)

                                //    - 执行后,栈顶压入生成的 Consumer 实例

11
: invokeinterface #20, 2      // 调用 Stream 接口的 forEach 方法(常量池#20对应方法):
                                //    - 方法签名:Stream.forEach(Consumer<? super T>) → void(遍历流中元素,执行 Consumer 的 accept 方法)

                                //    - 参数个数2:接口方法调用的参数 = 调用者(stream 实例) + 显式参数(Consumer 实例)

                                //    - 执行逻辑:Stream 遍历每个元素,调用 Consumer.accept(Integer)(即 Lambda 逻辑)

16
: return                      // 返回


// 私有静态方法 lambda$test4$1:

private
 static void lambda$test4$1(java.lang.Integer); 
    Code:
       0
: return                // 空逻辑方法,直接返回

可以看到,stream.forEach相比list.forEach还多了维护流水线相关操作。

四种循环方式对比

循环类型底层依赖组件遍历方式核心开销来源并发修改校验方式
普通 for 循环
无(仅语言语法 + ArrayList 数组 + 拆箱方法)
索引直接访问
无额外开销(仅边界检查)
无(需手动控制)
增强 for 循环
Iterable + Iterator
迭代器 hasNext ()/next ()
接口调用(hasNext/next)
Iterator 的 expectedModCount 校验
List.forEach
Consumer + 集合自身遍历
数组直接遍历 / 迭代器遍历
Consumer.accept () 方法调用
集合自身 modCount 校验
Stream.forEach
Stream + Spliterator + Sink
管道化 Sink 链传递
Stream 创建 + Sink 链转发 + Spliterator 分割
Spliterator 的 modCount 校验

04

结果总结

插入知识点:JIT 是 Just-In-Time Compiler 的缩写「即时编译器」,是 Java 虚拟机(JVM)中核心的性能优化组件 —— 它的核心目标是解决 “解释执行 Java 字节码速度慢” 的问题,通过将 “频繁执行的热点代码” 编译为高效的机器码,让 Java 程序运行速度接近原生语言(如 C/C++)。JIT 是 “惰性优化”—— 不启动就优化所有代码,只优化 “频繁执行的热点代码”,平衡 “启动速度” 和 “运行速度”。

通过底层代码和字节码的分析,可以总结测试现象的原因:

1. 普通 for 循环(最快)

核心优势:无额外开销 + JIT 深度优化

• 关键步骤(无冗余):

1. 循环条件判断:i < list.size() → size是 ArrayList 的成员变量,直接读取(耗时可忽略);

2. 数组访问:list.get(i) → 直接通过下标elementData[i]获取元素(O (1),无中间操作);

3. 拆箱:Integer → int(所有遍历都需执行,无差异);

4. 循环变量自增:i++ → 局部变量操作(耗时可忽略)。

• JIT 优化放大优势:

边界检查消除:JVM 可证明i < size不会越界,删除get(i)中的数组边界检查;

循环展开:将多次循环合并为一次(减少循环判断 / 自增的次数);

缓存友好:连续内存访问,CPU 缓存命中率接近 100%。

耗时核心:仅数组访问 + 拆箱,无任何额外开销。

2. 增强 for 循环(次快)

核心开销:迭代器的「fail-fast 检查」+ 方法调用

• 关键步骤(比普通 for 多 2 个核心开销):

1. 迭代器创建:new Itr() → 初始化cursor=0、expectedModCount=modCount(耗时低,但需创建对象);

2. 循环条件:it.hasNext() → 仅判断cursor != size(简单,耗时低);

3. 元素获取:it.next() → 核心开销点:

• checkForComodification():判断modCount == expectedModCount(防止遍历中修改集合,一千万次 int 比较的累积开销);

• cursor++ + 数组访问(和普通 for 一致);

4. 拆箱(和普通 for 一致)。

• 优化局限性:

• 迭代器的hasNext()/next()是方法调用(即使 JIT 内联,仍比普通 for 的直接操作多一层开销);

• modCount检查无法消除(fail-fast 机制的必须步骤)。

耗时核心:一千万次modCount检查 + 迭代器方法调用的累积开销。

3. list.forEach(较慢)

核心开销:Consumer 接口方法调用 + 循环内modCount检查

• 关键步骤(比增强 for 多 1 个核心开销):

1. Consumer 对象创建:若用 lambda 表达式(如 e -> {}),会编译为匿名内部类实例(jdk8 用 invokedynamic 优化,但仍有对象创建开销);

2. 并发修改检查:同增强 for 循环(modCount == expectedModCount);

3. 接口方法调用:JVM 每次执行 accept(x) 时,都需要通过「动态分派」找到 Lambda 动态生成的 accept() 实现(虽然类已初始化,但方法调用仍需查表),jdk 8 的 JIT 对「动态生成的 Lambda 方法」内联支持极差 ,因为 Lambda 的 accept() 是动态生成的,JIT 无法在编译期识别其逻辑,只能按普通虚方法处理,无法消除调用开销。

4. 自动拆箱(和其他方式一致)。

耗时核心:一千万次accept()方法调用 + 循环内modCount检查。

4. stream.foreach(最慢)

核心开销:Stream 框架额外开销 + Spliterator 管理

• 关键步骤(比 list.forEach 多框架级开销):

1. Stream 创建:list.stream() → 生成 Spliterator(ArrayList 的索引型 Spliterator)、创建 Stream 管道(ReferencePipeline)→ 固定框架开销;

2. Spliterator 遍历:spliterator.tryAdvance(action) → 需管理 Spliterator 的索引范围、状态(比直接下标遍历多一层包装);

3. 元素处理:action.accept() → 和 list.forEach () 一致的方法调用开销;

4. modCount检查:Spliterator 同样会维护expectedModCount,防止并发修改;

5. 拆箱(和其他方式一致)。

• 优化局限性:

• Stream 框架代码复杂,JIT 难以像普通 for 那样做深度优化(如循环展开、边界检查消除);

• 即使是串行 Stream,仍会加载并行相关的逻辑(无实际执行,但有初始化开销)。

耗时核心:Stream 框架初始化 + Spliterator 管理开销。

性能差距的核心根源总结

四种遍历的耗时排序(普通 for < 增强 for < list.forEach < stream.foreach),本质是 「额外开销的叠加」+「JIT 优化的递减」:

性能影响因素普通 for增强 forlist.forEachstream.foreach
中间对象(迭代器 / Stream 等)
无
迭代器
Consumer
Stream+Spliterator
modCount 检查
无
有(next () 中)
有(循环条件中)
有(Spliterator 中)
额外方法调用
无
hasNext()/next()
accept()
tryAdvance()+accept()
JIT 优化程度
最好
较好
中等
较差

jdk 8 中,forEach和stream.forEach的慢本质是「函数式编程的抽象层开销」在大数据量下的持续叠加 ——Lambda 的虚方法调用、Stream 的 Spliterator 遍历,这些开销在 jdk 8 中无法被 JIT 优化消除;而普通 for 循环因「无抽象层 + JIT 深度优化」,成为大数据量遍历的最优选择。

05

再次测试

还是jdk8下,每种循环都先执行一遍空循环遍历,再次执行,记录第二次耗时。

结果: 普通for循环和增强for循环基本没什么变化。 但是forEach和stream.forEach的耗时却增加很多,和增强for循环基本相同了。

为什么呢? 这是JIT的功劳。

JIT 对 list.forEach 的核心优化(第二次提速关键)

根据上边分析可知:list.forEach 第一次慢的核心是「接口多态调用」和「Consumer 对象创建开销」,第二次 JIT 会针对性解决这两个问题:

1. 去虚拟化:消除 Consumer.accept() 的多态开销

第一次执行时,Consumer 是 lambda 表达式对应的匿名内部类实例(如 Lambda$1@xxx),action.accept() 是 接口多态调用(JIT 不确定 Consumer 的具体实现,无法内联,只能通过接口查找实现类,开销高)。

第二次执行前,JIT 会通过「类型 profile」(记录方法调用的实际类型)发现:当前 Consumer 只有唯一实现(就是那个空逻辑的 lambda)。此时 JIT 会做「单态去虚拟化」—— 直接将多态调用替换为「具体实现类的方法调用」,甚至进一步内联。

优化前后对比(伪代码):

• 第一次(解释执行):action.accept(element) → 接口查找实现 → 调用 Lambda$1.accept()(多态开销);

• 第二次(编译执行):JIT 直接内联 Lambda$1.accept() 的逻辑(空逻辑),此时 list.forEach 的核心开销只剩「循环控制」和「modCount 检查」,与增强 for 循环(Itr.hasNext()+Itr.next())的开销接近。

2. 逃逸分析(Escape Analysis):消除 Consumer 对象创建开销

第一次执行时,lambda 会被实例化为 Consumer 对象(堆上分配),有对象创建和垃圾回收的潜在开销。

第二次 JIT 会通过「逃逸分析」发现:这个 Consumer 对象只在 list.forEach 方法内使用,没有 “逃逸” 到方法外部(比如没有被存储到全局变量、没有被其他线程访问)。此时 JIT 会做「栈上分配」或「标量替换」—— 直接消除 Consumer 对象的堆分配,把对象的字段拆成局部变量(甚至直接消除对象),进一步降低开销。

JIT 对 stream.forEach 的核心优化(第二次提速关键)

stream.forEach 第一次慢的原因是「Stream 初始化 + Spliterator 多层调用」,第二次 JIT 会优化这两层额外开销:

1. 优化 Stream 初始化开销

第一次执行 list.stream() 时,会创建 ReferencePipeline.Head(Stream 管道头)、ArrayListSpliterator(拆分迭代器)等对象,初始化逻辑有一定开销。

第二次 JIT 会:

• 通过「逃逸分析」消除 Stream 相关对象的堆分配(这些对象仅在遍历期间使用,无逃逸);

• 编译优化 Stream 初始化的冗余逻辑(比如合并常量、消除不必要的参数检查),使得 list.stream() 的初始化开销几乎可以忽略。

2. 内联 Spliterator.tryAdvance () 方法

stream.forEach 依赖 ArrayListSpliterator.tryAdvance(action) 迭代,第一次执行时这是一层额外的方法调用(tryAdvance() → accept())。

第二次因为 tryAdvance() 是热点方法,且 ArrayListSpliterator 是具体类(非接口),JIT 会直接将其「内联」到 Stream 的 forEach 终端操作中,此时两层方法调用(tryAdvance()+accept())被合并为一层,甚至直接内联为循环内的逻辑,开销大幅降低。

3. 同 list.forEach 的去虚拟化优化

Consumer.accept() 的多态调用同样被 JIT 去虚拟化并内联,消除接口调用开销。优化后其开销与 list.forEach、增强 for 循环基本一致。

06

再再次测试

这次使用jdk17版本,其他条件不变,还是只测试一遍空循环遍历, 得到结果:

循环方式单次执行耗时(ms)
普通for循环
4
增强for循环
19
list.forEach
20
stream.forEach
22

可以发现,jdk17下,forEach和stream.forEach的性能已经和增强for循环基本相同了,那jdk偷偷做了哪些升级呢?

性能提升原因分析

JDK 17 作为长期支持(LTS)版本,针对 Stream API 和 Lambda 表达式的执行效率实现了全方位优化,核心原因可归结为:

1. C2 编译器对函数式代码的深度优化

JDK 17 默认沿用 HotSpot 的 C2 编译器(服务端模式),并继承了 JDK 9-16 期间的优化成果,其对函数式编程相关优化逻辑进行了大幅增强:

• lambda 内联优化:C2 能将 Consumer.accept() 等函数式接口方法与 Stream 内部迭代逻辑直接内联,消除虚函数调用的性能开销;

• Stream 流水线扁平化:智能识别无中间操作的 Stream(如 DATA.stream().forEach(...)),直接优化为普通迭代逻辑,跳过 Stream 流水线封装带来的额外开销;

• 逃逸分析增强:将简单 Consumer 等函数式接口实例判定为“不逃逸”,通过标量替换技术避免堆分配(效果等同于分配在栈上),减少垃圾回收(GC)压力。

• GraalVM 可选增强:若部署环境采用 GraalVM,可进一步利用其先进的全局优化算法,在特定场景下获得比 C2 更优的函数式代码执行性能。

2. Stream API 的底层实现优化

JDK 17 继承了自 JDK 9 以来对 Stream 内部实现的持续微调:

• 流水线简化:针对无中间操作或简单链式调用(如 stream().forEach()),直接复用集合的迭代器,避免额外封装;

• Spliterator 优化:改进了迭代器的分割与遍历逻辑,提升遍历效率;

• 类结构精简:内部 ReferencePipeline 等类简化,减少了对象创建的隐性成本。

3. 装箱与拆箱的性能提升

JDK 17 对包装类(如 Integer)的自动装箱/拆箱处理更高效,结合 JIT 的类型推断,减少了部分场景下的方法调用开销。若使用基本类型流(IntStream),优化效果更显著,可直接访问底层基本类型数据,避免 intValue() 拆箱开销。

4. 并行 Stream 的调度优化

jdk17 优化了 Fork/Join 框架(并行 Stream 的底层依赖)的线程调度:

• 减少线程创建和切换开销;

• 优化任务拆分策略,避免小任务的过度拆分;

• 利用 CPU 缓存局部性,提升并行执行效率。

5. JVM 层面的整体优化

JDK 17 在逃逸分析、方法内联、循环展开等方面均有增强,这些优化对所有循环类型都有增益,但对 Stream 这类复杂逻辑的提升更为明显。

到这也就知道了,文章开头的优化没什么效果的原因: 测试环境使用的是jdk17且参数数据量比较小,这部分代码耗时占接口调用占比极小。(草率了)

07

场景选择

结合四种循环的特性、jdk 版本差异与性能表现,开发中可遵循 “性能优先、兼顾可读性” 的原则,按以下场景选型:

优先 传统 for 循环 的场景

1. 遍历数组或支持随机访问的集合(ArrayList),且需要索引(如修改元素、跳过 / 反向遍历);

2. 对性能要求极致(如高频遍历、海量数据处理),且 jdk 版本低于 11;

3. 需手动控制循环进度(如多线程场景下的分段遍历)。

优先 增强 for 循环 的场景

1. 遍历集合(List、Set)或数组,无需索引,追求代码简洁;

2. 需兼容低版本 jdk,且可读性优先级高于极致性能;

3. 遍历非随机访问集合(如 LinkedList),传统 for 的get(index)会导致 O (n²) 性能问题,增强 for(迭代器)更高效。

优先 List.forEach 的场景

1. jdk8 + 环境,遍历 List 且无需索引,追求函数式编程风格;

2. 遍历逻辑简单(如打印、简单过滤),方法引用(out::println)可进一步简化代码;

3. 需兼顾可读性与性能(jdk11 + 环境下性能接近增强 for)。

优先 Stream.forEach 的场景

1. 需对数据进行链式操作(过滤、映射、排序、聚合),如stream().filter().map().forEach();

2. 海量数据处理,可通过parallelStream()利用多核 CPU(注意线程安全与锁开销);

3. jdk17 + 环境,性能已接近传统循环,且代码可读性、扩展性更优;

4. 函数式编程场景(如结合 Optional、Stream API 构建流式数据处理管道)。

08

总结一下

Java 循环的演进,本质是 “性能与可读性的平衡”:从传统 for 的极致性能,到增强 for 的简洁,再到 List.forEach 与 Stream.forEach 的函数式灵活,每一种循环都对应特定的开发场景。随着 jdk 版本的升级(尤其是 jdk17 的性能飞跃),函数式遍历的性能短板已大幅弥补,开发中可更自由地在 “性能” 与 “代码优雅” 之间做出选择。最终,循环选型的核心不是 “哪一种更快”,而是 “哪一种更适合当前场景”—— 在满足性能需求的前提下,简洁、易维护的代码才是长期价值的关键。


Image