Java中l(wèi)ist.foreach()和list.stream().foreach()用法詳解
前言
典故來源于項(xiàng)目中使用了兩種方式的foreach,后面嘗試體驗(yàn)下有何區(qū)別!
先看代碼示例:
使用List的forEach:
import java.util.Arrays; import java.util.List; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 使用List的forEach data.forEach(element -> { System.out.println("Element: " + element); }); } }
使用Stream API的forEach:
import java.util.Arrays; import java.util.List; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 使用Stream API的forEach data.stream().forEach(element -> { System.out.println("Element: " + element); }); } }
兩者輸出結(jié)果都為如下:
既然兩個(gè)都輸出差不多的結(jié)果,但兩者還是稍微有些小區(qū)別,具體看下文!
1. 基本知識(shí)
forEach()
是List接口的一部分,用于對(duì)列表中的每個(gè)元素執(zhí)行給定的操作。與Stream API的 forEach 不同,list的 forEach 不支持并行處理,因此它在處理大量數(shù)據(jù)時(shí)可能不如Stream API高效。
適用于簡單的迭代和操作,不需要復(fù)雜的流處理。stream().forEach()
是Stream API的一部分,用于對(duì)流中的每個(gè)元素執(zhí)行給定的操作。使用Stream API的 forEach 允許進(jìn)行更多的操作,例如過濾、映射等,因?yàn)槟憧梢栽诹魃湘準(zhǔn)秸{(diào)用其他方法。
Stream API提供了更多的靈活性和功能,適用于處理大量數(shù)據(jù)或進(jìn)行復(fù)雜的操作。
兩者更多的區(qū)別可看下表:
代碼塊 | 概念 | 作用 | 差異之處 | 適用場景 |
---|---|---|---|---|
list.foreach() | List接口中的方法,用于對(duì)列表中的每個(gè)元素執(zhí)行給定的操作 | 1.通過 forEach() ,可以對(duì)列表中的每個(gè)元素進(jìn)行遍歷,并在每個(gè)元素上執(zhí)行指定的操作。2.主要用于簡單的迭代和操作,適用于不需要復(fù)雜流處理的場景。 | 1.并行處理: List的 forEach 不支持并行處理,因此在處理大量數(shù)據(jù)時(shí)可能不如Stream API高效。Stream API的并行處理機(jī)制可以更好地利用多核處理器的性能。 2.功能限制: List的 forEach 主要用于簡單的迭代操作,而沒有提供像Stream API那樣的豐富中間操作和終端操作,限制了其在復(fù)雜數(shù)據(jù)處理場景中的應(yīng)用。 | 適用于簡單的遍歷和操作,例如對(duì)列表中的元素進(jìn)行打印、計(jì)算簡單統(tǒng)計(jì)量等。 不適用于需要復(fù)雜處理邏輯的情況。 |
stream().foreach() | Java 8引入的Stream API中的方法,用于對(duì)流中的每個(gè)元素執(zhí)行給定的操作。 | 1.通過 forEach() ,可以對(duì)流中的每個(gè)元素進(jìn)行遍歷,并在每個(gè)元素上執(zhí)行指定的操作。2.Stream API提供了一種更為函數(shù)式的方式來處理數(shù)據(jù),允許鏈?zhǔn)秸{(diào)用其他方法,如過濾、映射、排序等。 | 1.鏈?zhǔn)秸{(diào)用: 使用Stream API的 forEach 允許在流上進(jìn)行鏈?zhǔn)秸{(diào)用其他方法,使得可以進(jìn)行更多的操作。這種鏈?zhǔn)秸{(diào)用是函數(shù)式編程的一種特征,使代碼更為靈活和表達(dá)性強(qiáng)。 2.功能豐富: Stream API提供了更多的中間操作和終端操作,例如 map、filter、reduce 等,這些操作可以組合使用,實(shí)現(xiàn)更復(fù)雜的數(shù)據(jù)處理邏輯。 | 適用于處理大量數(shù)據(jù)或進(jìn)行復(fù)雜的操作,例如對(duì)數(shù)據(jù)進(jìn)行變換、過濾、聚合等。 Stream API的并行處理機(jī)制也使得在多核處理器上能夠更高效地處理大規(guī)模數(shù)據(jù)。 |
總結(jié):
2. 差異之處
此處的差異之處主要講解stream().foreach()
比 list.foreach()
多的點(diǎn)
2.1 執(zhí)行順序
stream流中如果結(jié)合parallelStream()
,會(huì)有不一樣的特性!
import java.util.Arrays; import java.util.List; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 使用Stream API中的parallelStream forEach data.parallelStream().forEach(element -> { System.out.println("Element: " + element); }); } }
截圖如下:
在使用 parallelStream()
進(jìn)行并行處理時(shí),元素的處理順序可能不會(huì)按照原始列表中的順序輸出。
并行流會(huì)將數(shù)據(jù)分成多個(gè)塊,然后并行處理這些塊,最后將結(jié)果合并。
因此,并行處理的結(jié)果可能在不同的塊之間交錯(cuò),導(dǎo)致輸出的順序不再按照原始列表的順序。
想要保持順序,可以使用 forEachOrdered()
方法,它會(huì)保證按照流的遍歷順序輸出結(jié)果(并行流的情況下保持元素的遍歷順序,但可能會(huì)犧牲一些并行處理的性能優(yōu)勢)。示例如下:
import java.util.Arrays; import java.util.List; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 使用Stream API的parallelStream().forEachOrdered data.parallelStream().forEachOrdered(element -> { System.out.println("Element: " + element); }); } }
截圖如下:
2.2 串行并行
對(duì)應(yīng)上章節(jié)繼續(xù)科普parallelStream()
,一般來說并行的輸出速度比較快,用于對(duì)順序輸出不講究,但是大量的數(shù)據(jù)處理可以用這個(gè)!
對(duì)于少量的數(shù)據(jù)來說,效果不明顯,甚至?xí)哂?code>list.foreach(),所以根據(jù)情況而來,一般數(shù)據(jù)處理多,可以應(yīng)用parallelStream()
import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000000; i++) { numbers.add(i); } // 使用 Stream.forEach 進(jìn)行并行處理 long startTime = System.currentTimeMillis(); numbers.parallelStream().forEach(element -> { // 模擬一些耗時(shí)的操作 Math.pow(element, 2); }); long endTime = System.currentTimeMillis(); System.out.println("Time taken with Stream.forEach (parallel): " + (endTime - startTime) + " ms"); } }
截圖如下所示:
原本以為使用list.foreach()
,時(shí)間會(huì)更長,反而時(shí)間更短:
import java.util.ArrayList; import java.util.List; public class Demo { public static void main(String[] args) { List<Integer> numbers = new ArrayList<>(); for (int i = 1; i <= 1000000; i++) { numbers.add(i); } // 使用 List.forEach 進(jìn)行串行處理 long startTime = System.currentTimeMillis(); numbers.forEach(element -> { // 模擬一些耗時(shí)的操作 Math.pow(element, 2); }); long endTime = System.currentTimeMillis(); System.out.println("Time taken with List.forEach (sequential): " + (endTime - startTime) + " ms"); } }
截圖如下:
可能是由于數(shù)據(jù)規(guī)模較小、并行處理帶來的額外開銷以及模擬的耗時(shí)操作較短,導(dǎo)致并行流的性能沒有得到有效提升。
并行處理的優(yōu)勢在于處理大規(guī)模數(shù)據(jù)時(shí)更為顯著。
2.3 復(fù)雜數(shù)據(jù)處理
Stream API提供了豐富的中間操作和終端操作,使得能夠進(jìn)行更復(fù)雜的數(shù)據(jù)處理。
下面舉例說明 filter()、map()、reduce()
這幾個(gè)常用的操作方法。
filter()
用于對(duì)流中的元素進(jìn)行過濾,只保留滿足某個(gè)條件的元素:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 過濾出長度大于3的元素 List<String> filteredList = data.stream() .filter(element -> element.length() > 3) .collect(Collectors.toList()); System.out.println("Filtered List: " + filteredList); } }
截圖如下:
map()
用于對(duì)流中的每個(gè)元素進(jìn)行映射轉(zhuǎn)換,生成一個(gè)新的流:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five"); // 將每個(gè)元素轉(zhuǎn)換為大寫 List<String> uppercasedList = data.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println("Uppercased List: " + uppercasedList); } }
截圖如下:
reduce()
用于將流中的元素逐個(gè)進(jìn)行操作,最終得到一個(gè)結(jié)果
import java.util.Arrays; import java.util.List; import java.util.Optional; public class Demo { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 對(duì)所有元素求和 Optional<Integer> sum = numbers.stream() .reduce((x, y) -> x + y); sum.ifPresent(result -> System.out.println("Sum: " + result)); } }
使用 Optional<Integer>
主要是為了處理可能沒有元素的情況,避免返回 null。
考慮到 numbers 可能為空,如果直接使用 reduce((x, y) -> x + y)
,當(dāng) numbers 為空時(shí),會(huì)得到 null,而這可能導(dǎo)致空指針異常。
截圖如下:
拓展1
對(duì)于上述代碼中,都有使用到Collectors.toList()
Collectors.toList()
是Collectors 工具類提供的一個(gè)靜態(tài)方法,它用于將流中的元素收集到一個(gè) List 集合中。
在Stream API中,對(duì)流進(jìn)行一系列的操作后,通常會(huì)希望將結(jié)果收集到一個(gè)集合中,以便后續(xù)的操作或輸出。在實(shí)際應(yīng)用中,還可以使用其他的 Collectors 方法,如
toSet()、toMap()
等,以便根據(jù)需求將元素收集到不同的集合類型中。
拓展2
對(duì)于上述reduce中,使用到了Optional
類
Optional 是Java 8引入的一個(gè)類,用于處理可能為null的值。
它的設(shè)計(jì)目的是避免使用null,減少空指針異常的發(fā)生,并提供更安全、清晰的代碼。
在上述代碼中,通過使用 Optional<Integer>
,可以更安全地處理可能為空的情況。如果 numbers 為空,reduce 操作的結(jié)果將是 Optional.empty()
,而不是 null。
import java.util.Arrays; import java.util.List; import java.util.Optional; public class ReduceWithOptionalExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 使用Optional處理可能為空的情況 Optional<Integer> sum = numbers.stream() .reduce((x, y) -> x + y); // 判斷Optional是否包含值 if (sum.isPresent()) { System.out.println("Sum: " + sum.get()); } else { System.out.println("The list is empty."); } } }
2.4 CRUD集合
當(dāng)使用 list.foreach()
或 stream().foreach()
進(jìn)行遍歷操作時(shí),如果在遍歷過程中對(duì)集合進(jìn)行了增、刪、改的操作,可能會(huì)導(dǎo)致 ConcurrentModificationException
異常。
一、list.foreach()的增刪:
增加數(shù)據(jù)的Demo代碼:
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = new ArrayList<>(); data.add("One"); data.add("Two"); data.add("Three"); // 使用 List.forEach 遍歷,并在遍歷過程中增加元素 data.forEach(element -> { System.out.println("Element: " + element); if (element.equals("Two")) { data.add("Four"); // 在遍歷過程中增加元素 } }); } }
以及刪除數(shù)據(jù)Demo:
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = new ArrayList<>(); data.add("One"); data.add("Two"); data.add("Three"); // 使用 List.forEach 遍歷,并在遍歷過程中刪除元素 data.forEach(element -> { System.out.println("Element: " + element); if (element.equals("Two")) { data.remove(element); // 在遍歷過程中刪除元素,可能導(dǎo)致異常 } }); } }
最終的結(jié)果截圖如下:
二、list.stream().foreach()的增刪:
增加數(shù)據(jù)的Demo代碼:
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = new ArrayList<>(); data.add("One"); data.add("Two"); data.add("Three"); // 使用 Stream.forEach 遍歷,并在遍歷過程中增加元素 data.stream().forEach(element -> { System.out.println("Element: " + element); if (element.equals("Two")) { data.add("Four"); // 在遍歷過程中增加元素 } }); } }
截圖如下:
以及刪除數(shù)據(jù)Demo:
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = new ArrayList<>(); data.add("One"); data.add("Two"); data.add("Three"); // 使用 Stream.forEach 遍歷,并在遍歷過程中刪除元素 data.stream().forEach(element -> { System.out.println("Element: " + element); if (element.equals("Two")) { data.remove(element); // 在遍歷過程中刪除元素,可能導(dǎo)致異常 } }); } }
截圖如下:
通過上述四個(gè)程序的執(zhí)行結(jié)果,可以得到什么體會(huì)呢???
stream().foreach()
在操作集合的CRUD的時(shí)候,執(zhí)行錯(cuò)誤之后,還是會(huì)迭代整個(gè)列表,才看到異常這也證明
stream().foreach()
為并行執(zhí)行處理數(shù)據(jù),而不是串行那有辦法解決這種異常的情況么,又能對(duì)集合進(jìn)行CRUD!(答案是有的,可看如下正文)
2.5 迭代器
通過上述的閱讀,急需需要一個(gè)對(duì)集合的CRUD做一個(gè)安全性的迭代!
于是有了如下的解決方式:
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = new ArrayList<>(); data.add("One"); data.add("Two"); data.add("Three"); // 使用迭代器遍歷,并在條件滿足時(shí)刪除元素 Iterator<String> iterator = data.iterator(); while (iterator.hasNext()) { String element = iterator.next(); System.out.println("Element: " + element); if (element.equals("Two")) { iterator.remove(); // 嘗試在不支持結(jié)構(gòu)性修改的列表上進(jìn)行刪除操作,拋出異常 } } System.out.println("Modified List: " + data); } }
截圖如下:
3. 總結(jié)
總結(jié)起來,對(duì)于上面的代碼學(xué)習(xí),主要涉及了兩種遍歷集合的方式:List.forEach() 和 List.stream().forEach()。下面對(duì)這兩種方式的區(qū)別進(jìn)行總結(jié):
List.forEach() 方法:
遍歷是在當(dāng)前線程中按順序執(zhí)行的,對(duì)集合元素的操作是同步的。
適用于簡單的、順序執(zhí)行的遍歷操作。
不支持并行操作,不保證源數(shù)據(jù)的順序。
List.stream().forEach() 方法:
可以利用并行流進(jìn)行多線程處理,提高遍歷效率,不保證源數(shù)據(jù)的順序。
適用于更復(fù)雜的、并行處理的遍歷操作,可以配合 Stream 的其他操作進(jìn)行更靈活的數(shù)據(jù)處理。
4. 彩蛋
對(duì)于上述中的代碼,有一個(gè)需要注意的點(diǎn):
不管是list.foreach()
還是list.stream().foreach()
,集合的CRUD都會(huì)出現(xiàn)會(huì)爆:Exception in thread "main" java.lang.UnsupportedOperationException
的錯(cuò)誤,舉一個(gè)例子。
import java.util.*; public class Demo { public static void main(String[] args) { List<String> data = Arrays.asList("One", "Two", "Three"); // 使用 List.forEach 遍歷,并在遍歷過程中刪除元素 data.forEach(element -> { System.out.println("Element: " + element); if (element.equals("Two")) { data.remove(element); // 在遍歷過程中刪除元素,可能導(dǎo)致異常 } }); } }
截圖如下:
主要的原因在于:
Arrays.asList("One", "Two", "Three", "Four", "Five")
創(chuàng)建的列表是由數(shù)組支持的固定大小的列表。
這意味著該列表不支持結(jié)構(gòu)性修改操作(如添加、刪除),并且會(huì)在嘗試進(jìn)行這些操作時(shí)拋出UnsupportedOperationException
異常。
到此這篇關(guān)于Java中l(wèi)ist.foreach()和list.stream().foreach()用法詳解的文章就介紹到這了,更多相關(guān)Java中l(wèi)ist.foreach()和list.stream().foreach()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中IO流 RandomAccessFile類實(shí)例詳解
這篇文章主要介紹了Java中IO流 RandomAccessFile類實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05Java編程使用UDP建立群聊系統(tǒng)代碼實(shí)例
這篇文章主要介紹了Java編程使用UDP建立群聊系統(tǒng)代碼實(shí)例,具有一定借鑒價(jià)值,需要的朋友可以參考下。2018-01-01解析Java編程中對(duì)于包結(jié)構(gòu)的命名和訪問
這篇文章主要介紹了Java編程中對(duì)于包結(jié)構(gòu)的命名和訪問,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-12-12Spring?Cloud?Stream實(shí)現(xiàn)數(shù)據(jù)流處理
Spring?Cloud?Stream的核心是Stream,準(zhǔn)確來講Spring?Cloud?Stream提供了一整套數(shù)據(jù)流走向(流向)的API,?它的最終目的是使我們不關(guān)心數(shù)據(jù)的流入和寫出,而只關(guān)心對(duì)數(shù)據(jù)的業(yè)務(wù)處理,本文給大家介紹了Spring?Cloud?Stream實(shí)現(xiàn)數(shù)據(jù)流處理,需要的朋友可以參考下2024-11-11手把手教你如何在idea中搭建SpringBoot項(xiàng)目
這篇文章主要介紹了如何搭建一個(gè)SpringBoot項(xiàng)目,包括環(huán)境準(zhǔn)備、創(chuàng)建新項(xiàng)目、探索項(xiàng)目結(jié)構(gòu)以及展望未來,通過詳細(xì)的步驟和實(shí)用的技巧,幫助開發(fā)者快速上手SpringBoot開發(fā),文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02SpringBoot啟動(dòng)遇到的異常問題及解決方案
這篇文章主要介紹了SpringBoot啟動(dòng)遇到的異常問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02一文詳解Java如何優(yōu)雅地判斷對(duì)象是否為空
這篇文章主要給大家介紹了關(guān)于Java如何優(yōu)雅地判斷對(duì)象是否為空的相關(guān)資料,在Java中可以使用以下方法優(yōu)雅地判斷一個(gè)對(duì)象是否為空,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04