因为在 Lambda 表达式内部访问的外部局部变量必须是 final 或 effectively final(事实最终变量),而 i++ 操作试图改变这个变量的值,违反了这一规定。

下面我们来详细拆解这个问题,让你彻底明白。


1. 一个具体的例子

我们先看一段会报错的代码:

java

List<String> list = Arrays.asList("A", "B", "C");
int i = 0;
list.forEach(item -> {System.out.println(item);i++; // 编译错误:Variable used in lambda expression should be final or effectively final
});

编译器会直接在 i++ 这一行报错。


2. 深入原理:为什么要有这个限制?

这背后有两个关键原因:变量捕获并发安全

原因一:变量捕获与值拷贝
  • 在传统 for 循环中,变量 i 是堆栈上的一个局部变量,它的生命周期和作用域非常清晰。

  • 但在 Lambda 表达式中,情况不同了。Lambda 表达式可能不会立即执行(比如它被传递到一个方法中,在未来的某个时间点才被调用)。为了确保 Lambda 在执行时还能“看到”这个外部变量 i,Java 采用了一种叫做 “变量捕获” 的机制。

  • 捕获发生时,Java 并不是把变量 i 本身传递进 Lambda,而是将变量 i 的做一个拷贝,传递给 Lambda 表达式。

  • 现在想象一下,如果允许你在 Lambda 内部修改 i(比如 i++),你修改的只是 Lambda 内部的那个拷贝,而外部的原始变量 i 的值并没有改变。这会造成极大的困惑和歧义:你看的是同一个变量,但值却不一样。

为了保证数据的一致性,Java 语言设计者干脆规定:被捕获的变量必须是不可变的(final or effectively final)。这样就不存在“修改拷贝还是修改原值”的困惑了,因为大家看到的都是一个永远不会改变的值。

原因二:并发安全
  • Lambda 表达式,尤其是在与 Stream API 结合使用时,很容易在多线程环境下并行执行。

  • 假设允许在 Lambda 中修改外部变量,那么多个线程将会同时竞争修改同一个变量 ii++ 这个操作本身(读取、增加、写入)就不是原子性的,这必然会导致严重的竞态条件,得到不可预知的结果。

  • 强制使用 final 或 effectively final 变量,就从根源上杜绝了这种线程不安全的数据修改,鼓励开发者使用更安全的方式(如 reduction 操作 reduce()collect())来汇总结果,而不是依赖易变的外部状态。


3. 什么是 Effectively Final?

这是 Java 8 引入的一个概念。你不需要显式地用 final 关键字声明一个变量,只要这个变量在初始化后再也没有被修改过,编译器就认为它是“事实最终变量”。

在你的例子中,i++ 试图修改 i,破坏了 i 的 “effectively final” 状态,所以编译器报错。


4. 如果我确实需要在 forEach 中计数,该怎么办?

使用原子类(Atomic Classes)

创建一个可变的容器,但这个容器的原子操作是线程安全的。AtomicInteger 就是一个不错的选择。

java

List<String> list = Arrays.asList("A", "B", "C");
AtomicInteger atomicCount = new AtomicInteger(0); // 创建一个原子整数list.forEach(item -> {System.out.println(item);atomicCount.getAndIncrement(); // 原子性自增,相当于 i++
});System.out.println("Count: " + atomicCount.get()); // 输出:Count: 3

注意:这解决了编译问题,但如果 forEach 是并行流(parallelStream().forEach(...)),虽然 getAndIncrement 是原子的,整个计数逻辑在并发下仍然可能是乱序的。对于单纯计数,更好的并行做法是 list.stream().count()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96742.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96742.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/96742.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十四届蓝桥杯青少组C++选拔赛[2023.1.15]第二部分编程题(2 、寻宝石)

参考程序&#xff1a;#include <bits/stdc.h> using namespace std;int main() {int N;cin >> N; // 读入盒子数vector<int> a(N);for (int i 0; i < N; i) cin >> a[i]; // 读入每个盒子的宝石数// N > 3&#xff08;题目保证&#x…

9120 部 TMDb 高分电影数据集 | 7 列全维度指标 (评分 / 热度 / 剧情)+API 权威源 | 电影趋势分析 / 推荐系统 / NLP 建模用

一、引言在影视行业分析与数据科学实践中&#xff0c;高分电影数据的深度挖掘已成为平台优化内容推荐、制片方研判市场趋势、影迷发现优质作品的核心支撑 —— 通过上映年份与评分的关联可捕捉电影质量演变、依托热度与投票数能定位爆款潜质、结合剧情概述可开展情感与主题分析…

Tomcat PUT方法任意写文件漏洞学习

1 PUT请求 PUT请求是一种在HTTP协议中常见的请求方法 1.1 基本原理 PUT请求是一种用于向指定资源位置上传新的实体数据的请求方法&#xff0c;与其他请求方法的区别在于&#xff0c;PUT请求用于创建或者更新只当资源位置的实体数据。它与GET请求不同&#xff0c;PUT请求会替换掉…

【C++基础】初识模板——一起步入泛型编程的大门

引言在 C 世界里&#xff0c;模板&#xff08;Template&#xff09;就像一把万能钥匙。它允许你编写通用的代码&#xff0c;让编译器在需要的时候为具体类型生成对应的函数或类。换句话说&#xff0c;模板是 C 泛型编程&#xff08;Generic Programming&#xff09; 的基石。 如…

项目管理框架如何影响团队协作

在项目执行过程中&#xff0c;项目管理框架不仅是一套工具和流程&#xff0c;更是团队协作方式的基础。不同的项目管理框架会深刻影响团队沟通效率、任务分配、决策方式和整体协同效果。 传统框架通常强调层级与计划&#xff0c;带来高度规范化的协作&#xff1b;敏捷框架则强调…

正向代理,反向代理,负载均衡还有nginx

这是一个非常核心且重要的后端/运维知识领域。我会用尽可能清晰易懂的方式&#xff0c;结合生动的比喻&#xff0c;为你详细梳理这些概念。核心概念一览我们先从一个宏观的角度来理解它们之间的关系&#xff1a;代理&#xff08;Proxy&#xff09;&#xff1a; 一个中间人的角色…

WebSocket压缩传输优化:机器视觉高清流在DCS中的低延迟方案

引言在现代工业自动化领域&#xff0c;分布式控制系统&#xff08;DCS&#xff09;正面临着前所未有的数据挑战。随着机器视觉技术的广泛应用&#xff0c;高清视频流已成为监控产品质量、检测设备异常和保障生产安全的重要手段。然而&#xff0c;将720P、1080P甚至4K分辨率的高…

《Linux常见命令》

ls 功能&#xff1a;列出目录下的子目录与文件&#xff0c;对于文件&#xff0c;还会列出文件名及其他信息。 语法&#xff1a;ls [选项] [目录或文件] 1.常用选项及说明选项说明-a列出目录下的所有文件&#xff0c;包括以 . 开头的隐含文件-d将目录象文件一样显示&#xff0c;…

Python数据分析:函数定义时的位置参数。

目录1 代码示例2 欢迎纠错3 免费爬虫4 论文写作/Python 学习智能体1 代码示例 直接上代码。 def pargs1(a, b):"""先看确定数量的位置参数。最简单的位置参数。a和b都叫而且只能叫“位置参数”。所谓确定数量&#xff0c;很明显&#xff0c;是两个就是两个&…

《没有架构图?用 netstat、ss、tcpdump 还原服务连接与数据流向》

&#x1f4e2; 你是否遇到过这些问题&#xff1f; 接手一个老项目&#xff0c;只有服务器账号&#xff0c;没有架构图&#xff1f;服务突然异常&#xff0c;但不知道它依赖哪些外部系统&#xff1f;想画数据流向图&#xff0c;却找不到文档&#xff1f; 别担心&#xff01;只要…

Redis列表(List):实现队列/栈的利器,底层原理与实战

Redis列表&#xff08;List&#xff09;&#xff1a;实现队列/栈的利器&#xff0c;底层原理与实战 1. Redis列表概述 1.1 什么是Redis列表 Redis列表&#xff08;List&#xff09;是一个有序的字符串元素集合&#xff0c;支持在头部和尾部进行高效的插入和删除操作。它可以…

OpenCV 图像双三次插值

文章目录 一、简介 二、实现代码 三、实现效果 参考资料 一、简介 在数学中,双三次插值是三次样条插值(一种将三次插值应用于数据集的方法)的扩展,用于在二维规则网格上插值数据点。插值曲面(指核形状,而非图像)比通过双线性插值或最近邻插值获得的相应曲面更平滑。双三…

【Java实战㊲】Spring Security:为Spring Boot应用筑牢安全防线

目录 一、Spring Security 概述 1.1 Spring Security 核心功能 1.2 Spring Security 与 Shiro 对比 二、Spring Boot 整合 Spring Security 基础 2.1 整合依赖导入 2.2 默认安全配置 2.3 自定义用户认证 2.4 自定义登录与注销 三、Spring Security 授权控制 3.1 基于角色的授权…

linux命令—stat

命令简介 stat是Linux中用于查看文件或文件系统的详细状态信息的强大命令。它比ls -l更全面&#xff0c;其输出信息包括但不限于&#xff1a;文件大小、权限、所有者、最后访问/修改/状态变更时间、inode号、所在设备信息等。 用法 stat命令的语法格式如下 stat [选项] 文件…

解决串口数据乱序问题

环境&#xff1a;jetson nano ubuntu 20.04python 3.12终于是找到解决串口乱序的最佳解决办法了&#xff0c;先来看看什么是串口乱序&#xff1a;这就是一个典型的串口乱序&#xff0c;我的发送端发送 的协议为0x55 0x51 ...0x55 0x52 ...0x55 0x53 ...0x55 0x54 ...在这四条协…

Spring的注解

声明Bean的注解 Component Controller Service Repository 后三种为Component的别名&#xff0c;之所以不同是因为可读性的考虑 Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented Component public interface Controller {AliasFor(//别名an…

UVM寄存器模型与通道机制

接续UVM基础入门文章。前言重点讲述UVM常用的接口连接方式。寄存器模型&#xff1a;UVM寄存器模型&#xff08;Register Model&#xff09;是一组高级抽象的类&#xff0c;用于对DUT&#xff08;Design Under Test&#xff09;中具有地址映射的寄存器和存储器进行建模&#xff…

12.NModbus4在C#上的部署与使用 C#例子 WPF例子

一、Modbus TCP/IP是什么Modbus TCP/IP是一种基于TCP/IP协议的工业自动化通信协议。它在Modbus协议的基础上&#xff0c;利用TCP/IP网络进行数据传输&#xff0c;使得工业设备之间的通信更加便捷和高效。常用的Modbus功能码包括0x03&#xff08;读保持寄存器&#xff09;、0x06…

硬件开发2-汇编1(ARMv7-A)- 基本概要

一、汇编基本概要1、ARM数据和指令类型2、ARM字节顺序即可大端存储也可小端存储&#xff0c;默认小端存储&#xff08;不建议修改&#xff09;、kernel&#xff08;内核&#xff09;中的&#xff0c;CPSR&#xff08;当前程序状态寄存器&#xff09;可修改大小端存储3、ARM处理…

Linux中进程和线程常用的API详解

进程与线程基础及 Linux 进程间通信&#xff08;IPC&#xff09;详解 一、程序与进程 1. 程序&#xff08;静态文件&#xff09; 程序是存储在磁盘上的可执行文件&#xff0c;是静态实体&#xff0c;不占用 CPU、内存等运行时资源&#xff0c;仅占用磁盘空间。不同操作系统的可…