Java forEach中不能用i++的原因以及代替方案

因為在 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;僅占用磁盤空間。不同操作系統的可…