Java中l(wèi)ist.foreach()和list.stream().foreach()用法詳解
前言
典故來源于項目中使用了兩種方式的foreach,后面嘗試體驗下有何區(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é)果都為如下:
既然兩個都輸出差不多的結(jié)果,但兩者還是稍微有些小區(qū)別,具體看下文!
1. 基本知識
forEach()
是List接口的一部分,用于對列表中的每個元素執(zhí)行給定的操作。與Stream API的 forEach 不同,list的 forEach 不支持并行處理,因此它在處理大量數(shù)據(jù)時可能不如Stream API高效。
適用于簡單的迭代和操作,不需要復(fù)雜的流處理。stream().forEach()
是Stream API的一部分,用于對流中的每個元素執(zhí)行給定的操作。使用Stream API的 forEach 允許進行更多的操作,例如過濾、映射等,因為你可以在流上鏈式調(diào)用其他方法。
Stream API提供了更多的靈活性和功能,適用于處理大量數(shù)據(jù)或進行復(fù)雜的操作。
兩者更多的區(qū)別可看下表:
代碼塊 | 概念 | 作用 | 差異之處 | 適用場景 |
---|---|---|---|---|
list.foreach() | List接口中的方法,用于對列表中的每個元素執(zhí)行給定的操作 | 1.通過 forEach() ,可以對列表中的每個元素進行遍歷,并在每個元素上執(zhí)行指定的操作。2.主要用于簡單的迭代和操作,適用于不需要復(fù)雜流處理的場景。 | 1.并行處理: List的 forEach 不支持并行處理,因此在處理大量數(shù)據(jù)時可能不如Stream API高效。Stream API的并行處理機制可以更好地利用多核處理器的性能。 2.功能限制: List的 forEach 主要用于簡單的迭代操作,而沒有提供像Stream API那樣的豐富中間操作和終端操作,限制了其在復(fù)雜數(shù)據(jù)處理場景中的應(yīng)用。 | 適用于簡單的遍歷和操作,例如對列表中的元素進行打印、計算簡單統(tǒng)計量等。 不適用于需要復(fù)雜處理邏輯的情況。 |
stream().foreach() | Java 8引入的Stream API中的方法,用于對流中的每個元素執(zhí)行給定的操作。 | 1.通過 forEach() ,可以對流中的每個元素進行遍歷,并在每個元素上執(zhí)行指定的操作。2.Stream API提供了一種更為函數(shù)式的方式來處理數(shù)據(jù),允許鏈式調(diào)用其他方法,如過濾、映射、排序等。 | 1.鏈式調(diào)用: 使用Stream API的 forEach 允許在流上進行鏈式調(diào)用其他方法,使得可以進行更多的操作。這種鏈式調(diào)用是函數(shù)式編程的一種特征,使代碼更為靈活和表達性強。 2.功能豐富: Stream API提供了更多的中間操作和終端操作,例如 map、filter、reduce 等,這些操作可以組合使用,實現(xiàn)更復(fù)雜的數(shù)據(jù)處理邏輯。 | 適用于處理大量數(shù)據(jù)或進行復(fù)雜的操作,例如對數(shù)據(jù)進行變換、過濾、聚合等。 Stream API的并行處理機制也使得在多核處理器上能夠更高效地處理大規(guī)模數(shù)據(jù)。 |
總結(jié):
2. 差異之處
此處的差異之處主要講解stream().foreach()
比 list.foreach()
多的點
2.1 執(zhí)行順序
stream流中如果結(jié)合parallelStream()
,會有不一樣的特性!
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()
進行并行處理時,元素的處理順序可能不會按照原始列表中的順序輸出。
并行流會將數(shù)據(jù)分成多個塊,然后并行處理這些塊,最后將結(jié)果合并。
因此,并行處理的結(jié)果可能在不同的塊之間交錯,導(dǎo)致輸出的順序不再按照原始列表的順序。
想要保持順序,可以使用 forEachOrdered()
方法,它會保證按照流的遍歷順序輸出結(jié)果(并行流的情況下保持元素的遍歷順序,但可能會犧牲一些并行處理的性能優(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 串行并行
對應(yīng)上章節(jié)繼續(xù)科普parallelStream()
,一般來說并行的輸出速度比較快,用于對順序輸出不講究,但是大量的數(shù)據(jù)處理可以用這個!
對于少量的數(shù)據(jù)來說,效果不明顯,甚至會高于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 進行并行處理 long startTime = System.currentTimeMillis(); numbers.parallelStream().forEach(element -> { // 模擬一些耗時的操作 Math.pow(element, 2); }); long endTime = System.currentTimeMillis(); System.out.println("Time taken with Stream.forEach (parallel): " + (endTime - startTime) + " ms"); } }
截圖如下所示:
原本以為使用list.foreach()
,時間會更長,反而時間更短:
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 進行串行處理 long startTime = System.currentTimeMillis(); numbers.forEach(element -> { // 模擬一些耗時的操作 Math.pow(element, 2); }); long endTime = System.currentTimeMillis(); System.out.println("Time taken with List.forEach (sequential): " + (endTime - startTime) + " ms"); } }
截圖如下:
可能是由于數(shù)據(jù)規(guī)模較小、并行處理帶來的額外開銷以及模擬的耗時操作較短,導(dǎo)致并行流的性能沒有得到有效提升。
并行處理的優(yōu)勢在于處理大規(guī)模數(shù)據(jù)時更為顯著。
2.3 復(fù)雜數(shù)據(jù)處理
Stream API提供了豐富的中間操作和終端操作,使得能夠進行更復(fù)雜的數(shù)據(jù)處理。
下面舉例說明 filter()、map()、reduce()
這幾個常用的操作方法。
filter()
用于對流中的元素進行過濾,只保留滿足某個條件的元素:
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()
用于對流中的每個元素進行映射轉(zhuǎn)換,生成一個新的流:
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"); // 將每個元素轉(zhuǎn)換為大寫 List<String> uppercasedList = data.stream() .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println("Uppercased List: " + uppercasedList); } }
截圖如下:
reduce()
用于將流中的元素逐個進行操作,最終得到一個結(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); // 對所有元素求和 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)
,當 numbers 為空時,會得到 null,而這可能導(dǎo)致空指針異常。
截圖如下:
拓展1
對于上述代碼中,都有使用到Collectors.toList()
Collectors.toList()
是Collectors 工具類提供的一個靜態(tài)方法,它用于將流中的元素收集到一個 List 集合中。
在Stream API中,對流進行一系列的操作后,通常會希望將結(jié)果收集到一個集合中,以便后續(xù)的操作或輸出。在實際應(yīng)用中,還可以使用其他的 Collectors 方法,如
toSet()、toMap()
等,以便根據(jù)需求將元素收集到不同的集合類型中。
拓展2
對于上述reduce中,使用到了Optional
類
Optional 是Java 8引入的一個類,用于處理可能為null的值。
它的設(shè)計目的是避免使用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集合
當使用 list.foreach()
或 stream().foreach()
進行遍歷操作時,如果在遍歷過程中對集合進行了增、刪、改的操作,可能會導(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)致異常 } }); } }
截圖如下:
通過上述四個程序的執(zhí)行結(jié)果,可以得到什么體會呢???
stream().foreach()
在操作集合的CRUD的時候,執(zhí)行錯誤之后,還是會迭代整個列表,才看到異常這也證明
stream().foreach()
為并行執(zhí)行處理數(shù)據(jù),而不是串行那有辦法解決這種異常的情況么,又能對集合進行CRUD?。ù鸢甘怯械?,可看如下正文)
2.5 迭代器
通過上述的閱讀,急需需要一個對集合的CRUD做一個安全性的迭代!
于是有了如下的解決方式:
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"); // 使用迭代器遍歷,并在條件滿足時刪除元素 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)性修改的列表上進行刪除操作,拋出異常 } } System.out.println("Modified List: " + data); } }
截圖如下:
3. 總結(jié)
總結(jié)起來,對于上面的代碼學習,主要涉及了兩種遍歷集合的方式:List.forEach() 和 List.stream().forEach()。下面對這兩種方式的區(qū)別進行總結(jié):
List.forEach() 方法:
遍歷是在當前線程中按順序執(zhí)行的,對集合元素的操作是同步的。
適用于簡單的、順序執(zhí)行的遍歷操作。
不支持并行操作,不保證源數(shù)據(jù)的順序。
List.stream().forEach() 方法:
可以利用并行流進行多線程處理,提高遍歷效率,不保證源數(shù)據(jù)的順序。
適用于更復(fù)雜的、并行處理的遍歷操作,可以配合 Stream 的其他操作進行更靈活的數(shù)據(jù)處理。
4. 彩蛋
對于上述中的代碼,有一個需要注意的點:
不管是list.foreach()
還是list.stream().foreach()
,集合的CRUD都會出現(xiàn)會爆:Exception in thread "main" java.lang.UnsupportedOperationException
的錯誤,舉一個例子。
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)性修改操作(如添加、刪除),并且會在嘗試進行這些操作時拋出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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實現(xiàn)使用反射模擬IOC和getBean
這篇文章主要介紹了SpringBoot實現(xiàn)使用反射模擬IOC和getBean,IOC就是spring的核心思想之一——控制反轉(zhuǎn)。這里不再贅述,看此文章即可了解2023-04-04spring boot 日志/頁面處理、實體類構(gòu)建、后臺管理功能的實現(xiàn)
這篇文章主要介紹了spring boot 日志/頁面處理、實體類構(gòu)建、后臺管理功能的實現(xiàn),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08Java網(wǎng)絡(luò)編程之IO模型阻塞與非阻塞簡要分析
這篇文章主要介紹Java網(wǎng)絡(luò)編程中的IO模型阻塞與非阻塞簡要分析,文中附有示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09java?啟動參數(shù)?springboot?idea詳解
這篇文章主要介紹了java?啟動參數(shù)?springboot?idea的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09