详细分析Java中的list.foreach()和list.stream().foreach()

目录

  • 前言
  • 1. 基本知识
  • 2. 差异之处
    • 2.1 执行顺序
    • 2.2 串行并行
    • 2.3 复杂数据处理
    • 2.4 CRUD集合
    • 2.5 迭代器
  • 3. 总结
  • 4. 彩蛋

前言

典故来源于项目中使用了两种方式的foreach,后面尝试体验下有何区别!

先看代码示例:

使用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的forEachdata.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的forEachdata.stream().forEach(element -> {System.out.println("Element: " + element);});}
}

两者输出结果都为如下:

在这里插入图片描述

既然两个都输出差不多的结果,但两者还是稍微有些小区别,具体看下文!

1. 基本知识

  1. forEach() 是List接口的一部分,用于对列表中的每个元素执行给定的操作。与Stream API的 forEach 不同,list的 forEach 不支持并行处理,因此它在处理大量数据时可能不如Stream API高效。
    适用于简单的迭代和操作,不需要复杂的流处理。

  2. stream().forEach() 是Stream API的一部分,用于对流中的每个元素执行给定的操作。使用Stream API的 forEach 允许进行更多的操作,例如过滤、映射等,因为你可以在流上链式调用其他方法。
    Stream API提供了更多的灵活性和功能,适用于处理大量数据或进行复杂的操作。

两者更多的区别可看下表:

代码块概念作用差异之处适用场景
list.foreach()List接口中的方法,用于对列表中的每个元素执行给定的操作1.通过 forEach(),可以对列表中的每个元素进行遍历,并在每个元素上执行指定的操作。

2.主要用于简单的迭代和操作,适用于不需要复杂流处理的场景。
1.并行处理: List的 forEach 不支持并行处理,因此在处理大量数据时可能不如Stream API高效。Stream API的并行处理机制可以更好地利用多核处理器的性能。

2.功能限制: List的 forEach 主要用于简单的迭代操作,而没有提供像Stream API那样的丰富中间操作和终端操作,限制了其在复杂数据处理场景中的应用。
适用于简单的遍历和操作,例如对列表中的元素进行打印、计算简单统计量等。

不适用于需要复杂处理逻辑的情况。
stream().foreach()Java 8引入的Stream API中的方法,用于对流中的每个元素执行给定的操作。1.通过 forEach(),可以对流中的每个元素进行遍历,并在每个元素上执行指定的操作。

2.Stream API提供了一种更为函数式的方式来处理数据,允许链式调用其他方法,如过滤、映射、排序等。
1.链式调用: 使用Stream API的 forEach 允许在流上进行链式调用其他方法,使得可以进行更多的操作。这种链式调用是函数式编程的一种特征,使代码更为灵活和表达性强。

2.功能丰富: Stream API提供了更多的中间操作和终端操作,例如 map、filter、reduce 等,这些操作可以组合使用,实现更复杂的数据处理逻辑。
适用于处理大量数据或进行复杂的操作,例如对数据进行变换、过滤、聚合等。

Stream API的并行处理机制也使得在多核处理器上能够更高效地处理大规模数据。

总结:

2. 差异之处

此处的差异之处主要讲解stream().foreach()list.foreach()多的点

2.1 执行顺序

stream流中如果结合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  forEachdata.parallelStream().forEach(element -> {System.out.println("Element: " + element);});}
}

截图如下:

在这里插入图片描述

在使用 parallelStream() 进行并行处理时,元素的处理顺序可能不会按照原始列表中的顺序输出。
并行流会将数据分成多个块,然后并行处理这些块,最后将结果合并。

因此,并行处理的结果可能在不同的块之间交错,导致输出的顺序不再按照原始列表的顺序。

想要保持顺序,可以使用 forEachOrdered() 方法,它会保证按照流的遍历顺序输出结果(并行流的情况下保持元素的遍历顺序,但可能会牺牲一些并行处理的性能优势)。示例如下:

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().forEachOrdereddata.parallelStream().forEachOrdered(element -> {System.out.println("Element: " + element);});}
}

截图如下:

在这里插入图片描述

2.2 串行并行

对应上章节继续科普parallelStream(),一般来说并行的输出速度比较快,用于对顺序输出不讲究,但是大量的数据处理可以用这个!

对于少量的数据来说,效果不明显,甚至会高于list.foreach(),所以根据情况而来,一般数据处理多,可以应用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");}
}

截图如下:

在这里插入图片描述

可能是由于数据规模较小、并行处理带来的额外开销以及模拟的耗时操作较短,导致并行流的性能没有得到有效提升。

并行处理的优势在于处理大规模数据时更为显著。

2.3 复杂数据处理

Stream API提供了丰富的中间操作和终端操作,使得能够进行更复杂的数据处理。

下面举例说明 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() 用于对流中的每个元素进行映射转换,生成一个新的流:
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");// 将每个元素转换为大写List<String> uppercasedList = data.stream().map(String::toUpperCase).collect(Collectors.toList());System.out.println("Uppercased List: " + uppercasedList);}
}

截图如下:

在这里插入图片描述

  • reduce() 用于将流中的元素逐个进行操作,最终得到一个结果
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,而这可能导致空指针异常。

截图如下:

在这里插入图片描述

拓展1
对于上述代码中,都有使用到Collectors.toList()

Collectors.toList() 是Collectors 工具类提供的一个静态方法,它用于将流中的元素收集到一个 List 集合中。
在Stream API中,对流进行一系列的操作后,通常会希望将结果收集到一个集合中,以便后续的操作或输出。

在实际应用中,还可以使用其他的 Collectors 方法,如 toSet()、toMap() 等,以便根据需求将元素收集到不同的集合类型中。

拓展2
对于上述reduce中,使用到了Optional

Optional 是Java 8引入的一个类,用于处理可能为null的值。
它的设计目的是避免使用null,减少空指针异常的发生,并提供更安全、清晰的代码。

在上述代码中,通过使用 Optional<Integer>,可以更安全地处理可能为空的情况。如果 numbers 为空,reduce 操作的结果将是 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() 进行遍历操作时,如果在遍历过程中对集合进行了增、删、改的操作,可能会导致 ConcurrentModificationException 异常。

一、list.foreach()的增删:

增加数据的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"); // 在遍历过程中增加元素}});}
}

以及删除数据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); // 在遍历过程中删除元素,可能导致异常}});}
}

最终的结果截图如下:

在这里插入图片描述


二、list.stream().foreach()的增删:

增加数据的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"); // 在遍历过程中增加元素}});}
}

截图如下:

在这里插入图片描述
以及删除数据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); // 在遍历过程中删除元素,可能导致异常}});}
}

截图如下:

在这里插入图片描述

通过上述四个程序的执行结果,可以得到什么体会呢???

stream().foreach()在操作集合的CRUD的时候,执行错误之后,还是会迭代整个列表,才看到异常

这也证明stream().foreach()为并行执行处理数据,而不是串行

那有办法解决这种异常的情况么,又能对集合进行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(); // 尝试在不支持结构性修改的列表上进行删除操作,抛出异常}}System.out.println("Modified List: " + data);}
}

截图如下:

在这里插入图片描述

3. 总结

总结起来,对于上面的代码学习,主要涉及了两种遍历集合的方式:List.forEach() 和 List.stream().forEach()。下面对这两种方式的区别进行总结:

List.forEach() 方法:

  1. 遍历是在当前线程中按顺序执行的,对集合元素的操作是同步的。

  2. 适用于简单的、顺序执行的遍历操作。

  3. 不支持并行操作,不保证源数据的顺序。

List.stream().forEach() 方法:

  1. 可以利用并行流进行多线程处理,提高遍历效率,不保证源数据的顺序。

  2. 适用于更复杂的、并行处理的遍历操作,可以配合 Stream 的其他操作进行更灵活的数据处理。

4. 彩蛋

对于上述中的代码,有一个需要注意的点:
不管是list.foreach()还是list.stream().foreach(),集合的CRUD都会出现会爆: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); // 在遍历过程中删除元素,可能导致异常}});}
}

截图如下:

在这里插入图片描述

主要的原因在于:

Arrays.asList("One", "Two", "Three", "Four", "Five") 创建的列表是由数组支持的固定大小的列表。

这意味着该列表不支持结构性修改操作(如添加、删除),并且会在尝试进行这些操作时抛出UnsupportedOperationException 异常。

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

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

相关文章

Tomcat 简介安装

目录 1、概念介绍 Tomcat 组件 HTTP 请求过程 Tomcat 目录结构 Tomcat 命令 配置文件说明 2、安装环境 安装JDK 安装Tomcat 创建管理用户 3、搭建虚拟主机 1、概念介绍 端口&#xff1a;8080 Tomcat 服务器是一个免费的开放源代码的 Web 应用服务器, 按照 Sun 提供…

全双工通信协议:WebSocket

全双工通信协议&#xff1a;WebSockets 前言何时使用WebSocketsWebSocket APITextWebSocketHandlerWebSocketConfigurerWebSocket握手配置服务器允许的来源心跳包Java WebSocket API案例一&#xff1a;前端发送消息并接收后端响应案例二&#xff1a;模拟后端向前端推送消息案例…

Shell 虚拟机基线配置脚本示例

这是一个配置虚拟机基线的示例&#xff0c;包含关闭防火墙、禁用SElinux、设置时区、安装基础软件等。 这只是一个简单的模板&#xff0c;基线配置方面有很多&#xff0c;后续可以按照这个模板去逐步添加 代码示例 [rootbogon ~]# cat bastic.sh #!/bin/bashRED\E[1;31m GRE…

【C++】C++入门(一)

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. C关键字3. 命名空间3.1 命名空间定义3.2 命名空间的使用 4. C输入&输出 1. 前言 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0…

【产品交互】超全面B端设计规范总结

不知不觉已经深耕在B端这个领域3年有余&#xff0c;很多人接触过B端后会觉得乏味&#xff0c;因为B端的设计在视觉上并没有C端那么有冲击力&#xff0c;更多的是结合业务逻辑&#xff0c;设计出符合业务需求的交互&#xff0c;以及界面排版的合理性&#xff0c;达到产品的可用性…

助力工业焊缝质量检测,YOLOv7【tiny/l/x】不同系列参数模型开发构建工业焊接场景下工件表面焊接缺陷检测识别分析系统

焊接是一个不陌生但是对于开发来说相对小众的场景&#xff0c;在工件表面焊接场景下常常有对工件表面缺陷智能自动化检测识别的需求&#xff0c;工业AI结合落地是一个比较有潜力的场景&#xff0c;在我们前面的博文开发实践中也有一些相关的实践&#xff0c;感兴趣的话可以自行…

菜鸡后端的前端学习记录

前言 记录一下看视频学习前端的的一些笔记&#xff0c;以前对Html、Js、CSS有一定的基础&#xff08;都认得&#xff0c;没用过&#xff09;&#xff0c;现在不想从头再来了&#xff0c;学学Vue框架&#xff0c;不定时更新&#xff0c;指不定什么时候就鸽了。。。。 Vue2 01…

SQL Server多数据表之间的数据查询和分组查询

文章目录 一、多数据表之间的数据查询1.1内连接查询&#xff08;Inner join&#xff09;1.2 左外连接 (LEFT JOIN):1.3右外连接 (RIGHT JOIN):1.4. 全外连接 (FULL OUTER JOIN):1.5 交叉连接 (CROSS JOIN):1.6 自连接 (SELF JOIN):1.7 子查询: 二、分组查询2.1 分组查询2.2 查询…

静态分析C语言生成函数调用关系的利器——cflow(二)

大纲 环境准备选择项目分析代码简单分析高级分析坑&#xff1a;不能显示main函数所有调用函数的调用栈坑2&#xff1a;重定义错误坑3&#xff1a;缺失编译时产生的文件坑4&#xff1a;缺失工程的头文件包含路径指定坑5&#xff1a;操作系统的坑只存在于windows操作系统上的文件…

rabbitmq基础-java-1、快速入门

1、AMQP AMQP&#xff0c;即Advanced Message Queuing Protocol&#xff08;高级消息队列协议&#xff09;&#xff0c;一个提供统一消息服务的应用层标准高级消息队列协议&#xff0c;是应用层协议的一个开放标准&#xff0c;为面向消息的中间件设计&#xff0c;基于此协议的客…

Parallels Desktop 19 mac 虚拟机软件 兼容M1 M2

Parallels Desktop 19 for Mac 是一款适用于 macOS 的虚拟机软件。无需重启即可在 Mac 上运行 Windows、Linux 等系统&#xff0c;具有速度快、操作简单且功能强大的优点。包括 30 余种实用工具&#xff0c;可简化 Mac 和 Windows 上的日常任务。 软件下载&#xff1a;Parallel…

Linux目录结构:深入理解与命令创建指南

目录 摘要&#xff1a; 一.linux目录介绍 1.目录结果设置标准 2.目录结构介绍 二.linux命令 1.常见命令 # 与 $ 提示的区别 ifconfig查看ip地址 su 命令格式 cd 目录查看 查看文件内容 创建目录及文件 复制和移动 tar find chmod 2. vim一般使用 摘要&#xff1a; 前…

基于中文垃圾短信数据集的经典文本分类算法实现

垃圾短信的泛滥给人们的日常生活带来了严重干扰&#xff0c;其中诈骗短信更是威胁到人们的信息与财产安全。因此&#xff0c;研究如何构建一种自动拦截过滤垃圾短信的机制有较强的实际应用价值。本文基于中文垃圾短信数据集&#xff0c;分别对比了朴素贝叶斯、逻辑回归、随机森…

CentOS使用

1.使用SSH连接操作虚拟机中的CentOS 1.1 配置静态IP 想要使用ssh连接就需要获取虚拟机的IP&#xff0c;但若DHCP&#xff0c;则每次连接都要确定虚拟机的IP是否变化&#xff0c;故直接分配一个静态IP vmware中&#xff0c;编辑–虚拟网络编辑器&#xff0c;记住下方的子网掩…

windows和linux下SHA1,MD5,SHA256校验办法

今天更新android studio到Android Studio Hedgehog | 2023.1.1时&#xff0c;发现提示本机安装的git版本太老&#xff0c;于是从git官网下载最新的git。 git下载地址&#xff1a; https://git-scm.com/ 从官网点击下载最新windows版本会跳转到github仓库来下载发布的git&…

【趣味CSS3.0】粘性定位属性Position:sticky是不是真的没用了?

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

sublime text 开启vim模式

sublime text 开启vim模式 打开配置文件 mac下点击菜单栏 Sublime Text -> Settings... -> Settings 修改配置文件并保存 添加配置 // 开启vim模式 "ignored_packages": [// "Vintage", ], // 以命令模式打开文件 "vintage_start_in_comman…

视频监控平台EasyCVR增加fMP4流媒体视频格式及其应用场景介绍

近期我们在视频监控管理平台EasyCVR系统中新增了HTTP-FMP4播放协议&#xff0c;今天我们就来聊聊该协议的特点和应用。 fMP4&#xff08;Fragmented MPEG-4&#xff09;是基于MPEG-4 Part 12的流媒体格式&#xff0c;是流媒体的一项重要技术&#xff0c;因为它能通过互联网传送…

【GitHub项目推荐--12 年历史的 PDF 工具开源了】【转载】

最近在整理 PDF 的时候&#xff0c;有一些需求普通的 PDF 编辑器没办法满足&#xff0c;比如 PDF 批量合并、编辑等。 于是&#xff0c;我就去 GitHub 上看一看有没有现成的轮子&#xff0c;发现了这个 PDF 神器「PDF 补丁丁」&#xff0c;让人惊讶的是这个 PDF 神器有 12 年的…

RabbitMQ进阶篇【理解➕应用】

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于RabbitMQ的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 一.什么是交换机 1.概念释义 2.例…