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

news2024/11/23 21:59:56

目录

  • 前言
  • 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的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);
        });
    }
}

两者输出结果都为如下:

在这里插入图片描述

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

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  forEach
        data.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().forEachOrdered
        data.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.coloradmin.cn/o/1406493.html

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

相关文章

风丘车辆热管理测试方案

车辆热管理是在能源危机出现、汽车排放法规日益严格以及人们对汽车舒适性要求更高的背景下应运而生的。将各个系统或部件如冷却系统、润滑系统和空调系统等集成一个有效的热管理系统&#xff1b;控制和优化车辆的热量传递过程&#xff0c;保证各关键部件和系统安全高效运行&…

麒麟系统—— openKylin 安装到虚拟机以及开放SSH通过工具连接

麒麟系统—— openKylin 安装到虚拟机以及开放SSH通过工具连接 1. 在VMware中安装openKylin麒麟系统步骤1&#xff1a;准备VMware环境步骤2&#xff1a;创建新的虚拟机步骤3&#xff1a;安装openKylin麒麟系统步骤4&#xff1a;调整分别率步骤5&#xff1a;安装SSH 2. 使用Open…

安卓自动缩放布局

AutoScalingLayout 适用于 Android 的自动缩放布局。 替换布局&#xff1a; 我们只需要替换根布局所需的自动缩放&#xff0c;子布局也将实现自动缩放。 原始布局AutoScalingLayout相对布局ASRelativeLayout线性布局ASLinearLayoutFrameLayout&#xff08;框架布局&#xff…

SpringCloud Bus动态刷新全局广播

文章目录 代码地址配置项目配置修改测试 SpringCloud Bus动态刷新定点通知 代码地址 地址:https://github.com/13thm/study_springcloud/tree/main/days11_%20Bus 配置项目 必须先具备良好的RabbitMQ环境先 演示广播效果&#xff0c;增加复杂度&#xff0c;再以3355为模板再…

Python基础第九篇(Python可视化的开发)

文章目录 一、json数据格式&#xff08;1&#xff09;.转换案例代码&#xff08;2&#xff09;.读出结果 二、pyecharts模块介绍三、pyecharts模块入门&#xff08;1&#xff09;.pyecharts模块安装&#xff08;2&#xff09;.pyecharts模块操作&#xff08;1&#xff09;.代码…

nvm安装与使用教程

目录 nvm是什么 nvm安装 配置环境变量 更换淘宝镜像 安装node.js版本 nvm list available 显示可下载版本的部分列表 nvm install 版本号 ​编辑 nvm ls 查看已经安装的版本 ​编辑 nvm use 版本号(切换想使用的版本号) nvm是什么 nvm是node.js version management的…

c++学习笔记-STL案例-机房预约系统6-老师模块

前言 衔接上一篇“c学习笔记-STL案例-机房预约系统5-学生模块”&#xff0c;本文主要设计老师模块&#xff0c;从&#xff0c;老师登录和注销、查看所有预约、审核预约三个方面进行分析和实现。 目录 9 教师模块 9.1 教师登录和注销 9.1.1 构造函数 9.1.2 教师子菜单 ​编…

java web mvc-07-Vaadin 入门介绍

拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails 开源 The jdbc pool for java.(java …

通信入门系列——复变函数

本节目录 一、复变函数 1、复数 2、复数的四则运算 二、复指数函数 三、欧拉公式本节内容 一、复变函数 1、复数 复数单位i&#xff0c;也就是满足i^2-1&#xff0c;将zxiy表示为复数z&#xff0c;x和y为任意的实数&#xff0c;称为复数z的实部和虚部。由复数zxiy对应的点(x,y…

LiveGBS流媒体平台GB/T28181常见问题-如何配置使用自己已有的redis服务替换redis版本升级redis版本

LiveGBS如何配置使用自己已有的redis服务替换redis版本升级redis版本 1、Redis服务2、如何切换REDIS?2.1、停止启动REDIS2.2、配置信令服务2.3、配置流媒体服务2.4、启动 3、搭建GB28181视频直播平台 1、Redis服务 在LivGBS中Redis作为数据交换、数据订阅、数据发布的高速缓存…

TensorRT英伟达官方示例解析(一)

系列文章目录 TensorRT英伟达官方示例解析&#xff08;一&#xff09; TensorRT英伟达官方示例解析&#xff08;二&#xff09; 文章目录 系列文章目录前言一、参考资料二、配置系统环境三、00-MNISTData四、01-SimpleDemo4.1 Makefile4.2 main.cpp4.3 main.py 总结 前言 一、…

上位机图像处理和嵌入式模块部署(自定义算法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 我们在使用opencv的时候&#xff0c;虽然大部分算法都不需要我们自己重头开始编写&#xff0c;但是总有一些关于我们自己产品的know-how&#xff0…

Redis--Bitmap有序集合的语法和使用场景举例

文章目录 前言Bitmap概述Bitmap命令介绍使用场景结尾 前言 Redis除了常见的五种数据类型之外&#xff0c;其实还有一些少见的数据结构&#xff0c;如Geo&#xff0c;HyperLogLog&#xff0c;Bitmap等。虽然它们少见&#xff0c;但是作用却不容小觑。本文将介绍Bitmap数据类型的…

c语言-柔性数组

文章目录 前言一、柔性数组的介绍1.1 柔性数组的定义 二、柔性数组的使用2.1 使用说明2.2 结构体中的成员只包含一个柔性数组成员2.3 结构体中的成员包含其他成员和一个柔性数组成员 三、模拟柔性数组总结 前言 本篇文章介绍c语言中的柔性数组。 一、柔性数组的介绍 1.1 柔性…

【动态规划】【字符串】【C++算法】940. 不同的子序列 II

作者推荐 【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径 本文涉及知识点 动态规划汇总 LeetCode940. 不同的子序列 II 给定一个字符串 s&#xff0c;计算 s 的 不同非空子序列 的个数。因为结果可能很大&#xff0c;所以返回答案需要对 10^9 7 取…

MySQL之数据库DDL

文章目录 MySQL数据库基本操作数据定义DDL对数据库的常用操作创建表修改表格式结构 MySQL数据库基本操作 首先我们先了解SQL的语言组成&#xff0c;他分为四个部分 数据定义语言&#xff08;DDL&#xff09;数据操纵语言&#xff08;DML&#xff09;数据控制语言&#xff08;…

网络安全---防御保护--子接口小实验

子接口小实验&#xff1a; 环境准备&#xff1a; 防火墙区域配置为trust&#xff1a; PC设置其ip为同一个网段&#xff1a; 此时尝试ping无法ping通的原因是没有打开防火墙允许ping&#xff0c;我们在图形化界面允许ping即可 最终结果&#xff1a; .com域名服务器&#xff1a; …

机器视觉系统选型-参数-镜头各个参数之间相互关系

焦距越小&#xff0c;景深越大&#xff1b;焦距越小&#xff0c;畸变越大&#xff1b; 光圈越大&#xff0c;图像亮度越高&#xff1b;光圈越大&#xff0c;景深越小&#xff1b; 光圈越大&#xff0c;分辨率越高&#xff1b; 一般像场中心较边缘分辨率高&#xff0c;像场中心较…

K8S四层代理Service-02

Service的四种类型使用 ClusterIP使用示例Pod里使用service的服务名访问应用 NodePort使用示例 ExternalName使用示例 LoadBalancer K8S支持以下4种Service类型&#xff1a;ClusterIP、NodePort、ExternalName、LoadBalancer 以下是使用4种类型进行Service创建&#xff0c;应对…

网络协议与攻击模拟_06攻击模拟SYN Flood

一、SYN Flood原理 在TCP三次握手过程中&#xff0c; 客户端发送一个SYN包给服务器服务端接收到SYN包后&#xff0c;会回复SYNACK包给客户端&#xff0c;然后等待客户端回复ACK包。但此时客户端并不会回复ACK包&#xff0c;所以服务端就只能一直等待直到超时。服务端超时后会…