JDK都出到20多了,你还不会使用JDK8的Stream流写代码吗?

news2025/2/26 2:52:36

目录

前言

Stream流 是什么?

为什么要用Steam流

常见stream流使用案例

映射 map()  & 集合 collect()

单字段映射

多字段映射

映射为其他的对象

映射为 Map

去重 distinct()

过滤 filter()

Stream流的其他方法

使用Stream流的弊端


前言

当你某天看到舍友的代码不再写for循环时,你的反应:

你还在 new Collection<>() ,写着for循环的时候,舍友已经开始偷偷卷你,更改代码风格了

本文将带着大家简单理解 Stream 流,并通过部分案例描述 Stream 流 的实用方法

Stream流 是什么?

Stream 流Java 8 引入的一个强大工具,它提供了一种全新的方式来处理集合和数组等数据源,使得数据处理变得更加简单、高效和易于理解。


通俗的理解起来就是提供了一种更加便利的遍历处理方式。


如果你要问我 Stream流用起来什么感觉?

那我只能说,,这种感觉就像飞翔在~~

噢不对,,感觉就是:

为什么要用Steam流

 Stream 流的主要用途是提供一种高效且表达力高的方式来处理集合和数组等数据源。通过使用 Stream 流,可以避免显式的迭代器和循环,使得代码更加简洁、易读。Stream 流支持复杂的查询/过滤、映射/转换、归约/汇总等操作,能够极大地简化数据处理的复杂度。

总结起来还是:简洁、易读

当然,这也让你的代码看起来更高级那么一点点~~

如下案例,拿到所有的评论的id 集合的两种方法。

  • 第一种-for循环便利获取
		List<Comment> list = commentMapper.selectList(wrapper);
		List<Integer> commentId = new ArrayList<>();
		for(Comment c : list){
			commentId.add(c.getId());
		}
  • 第二种-Stream流获取
		List<Comment> list = commentMapper.selectList(wrapper);
		List<Integer> commentId = list.stream().map(Comment::getId).collect(Collectors.toList());

两种方法的区别显而易见

下面介绍stream流比较实用的方法

常见stream流使用案例

在这里我们准备一个简单的对象来进行案例测试,只约定两个字段。

@Data
@AllArgsConstructor
public class StreamTestObject {

    Integer id1;

    Integer id2;

}

映射 map()  & 集合 collect()

map() 方法是最常用的方法之一,它可以将流中的每个元素转换成另一种形式,返回转换后的 Stream

如前文的例子所示,

单字段映射
        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(5, 6));
        // 便于观察变化
        Stream<StreamTestObject> stream = streamTestObjects.stream();
        Stream<Integer> id1Stream = streamTestObjects.stream().map(StreamTestObject::getId1);

看代码我们可以看到,map方法将对象的stream流映射为了其中  id1 这个字段的stream流


拿到这个字段的流后,可以做些什么呢?

最常用的方法之一就是与 集合 collect() 搭配起来使用。

那么 collect() 方法能做写什么呢?

  • 用途:将流中的元素累积成一个汇总结果,我们可以按照自己的需求将结果汇总为一个 List、Set、Map 等

如下代码所示

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(5, 6));
        Stream<StreamTestObject> stream = streamTestObjects.stream();
        Stream<Integer> id1Stream = streamTestObjects.stream().map(StreamTestObject::getId1);

        List<Integer> collectList = id1Stream.collect(Collectors.toList());
//        Set<Integer> collectSet = id1Stream.collect(Collectors.toSet());

        // 连起来使用一行代码可以写成这样
        collectList = streamTestObjects.stream().map(StreamTestObject::getId1).collect(Collectors.toList());
//        collectSet = streamTestObjects.stream().map(StreamTestObject::getId1).collect(Collectors.toSet());
        System.out.println("collectList:" + collectList);
// 输出结果 collectList:[1, 3, 5]

结果能够把 id1 成功收集起来,代码的易读性也体现在其中。我们一眼就能看出这行代码映射了id1 这个字段为一个 List Set

多字段映射

那如果我们想要对象集合中的 id1 和 id2 都汇总到一个 List<Integer> 集合里,应该如何操作呢。

这里我们可以使用一个 flatMap() 方法

  • 用途:将流中的每个元素都转换成另一个流,然后将所有流连接成一个流。

直接上代码

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(5, 6));
        List<Integer> collectList = streamTestObjects.stream()
                .flatMap(object ->Stream.of(object.getId1(), object.getId2())).collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //输出结果 collectList:[1, 2, 3, 4, 5, 6]

在这个例子中,Stream.of(obj.getId1(), obj.getId2())为每个对象生成了一个包含两个ID的流,它在map中 形成了一个临时的流

然后flatMap将这些流“展平”成了一个包含所有ID的流,最后我们通过collect(Collectors.toList())将这个流收集到了一个列表中。

映射为其他的对象

有的时候的业务需求需要我们把一个对象集合转化为另外一个集合对象,如果是单纯的 字段copy,我们可以使用 BeanUtils 或者 MapStruct 等方法实现。

如果转化的过程中设计业务逻辑,那么就需要 Stream流出手了。

这里需要设计到一个显示 return 的写法,上代码,先准备一个另外的对象,只包含一个id字段

@Data
@NoArgsConstructor
@AllArgsConstructor
public class StreamOtherObject {

    Integer id;
    
}

然后 我们将上述测试对象的id1,转化为这里的id字段。

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(4, 5));
        List<StreamOtherObject> collectList = streamTestObjects.stream()
                .map(streamTestObject -> {
                    StreamOtherObject object = new StreamOtherObject();
                    object.setId(streamTestObject.getId1());
                    return object;
                }).collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //输出结果 collectList:[StreamOtherObject(id=1), StreamOtherObject(id=3), StreamOtherObject(id=4)]

注意看返回的集合对象已经是我在表达式里return 的 StreamOtherObject了。

也就是 return 的内容就是集合的具体对象

映射为 Map

Stream流还能把集合映射为一个Map,这里我们测试用例为将映射结果设置为 key 为 id1, value 为对象本身

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4));
        Map<Integer, StreamTestObject> map = streamTestObjects.stream()
                .collect(Collectors.toMap(
                        StreamTestObject::getId1,
                        Function.identity(),
                        (existsOne, replaceOne) -> replaceOne));
        System.out.println("collectMap:" + map);
        //输出结果 collectMap:{1=StreamTestObject(id1=1, id2=2), 3=StreamTestObject(id1=3, id2=4)}

可以看到,toMap() 方法中,传递了3个参数,前两个分别为 key value

第三个参数传了一个表达式,这里的逻辑表示如果发生冲突,就保留 Map中新的那个对象,而不是保留它。同时,第三个参数也处理了冲突,如果你没有对于 key 相同的情况做处理,也就是 key 冲突了,方法将抛出一个IllegalStateException

所以你需要做对应的处理,如try catch 下来,或者进行冲突处理,即传递第三个参数。

去重 distinct()

对于拿到的流结果,我们有的时候有去重的需求,当然我们可以转为 toSet() 进行去重

stream流同样提供了一个方法进行去重,就是 distinct() 方法

  • 用途:去除流中的重复元素,返回包含不同元素的 Stream

这里比较好理解,我们直接看案例

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(4, 5));
        List<Integer> collectList = streamTestObjects.stream()
                .flatMap(object ->Stream.of(object.getId1(), object.getId2())).distinct().collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //输出结果 collectList:[1, 2, 3, 4, 5]

过滤 filter()
  • 用途:根据提供的条件过滤元素,返回满足条件的 Stream。

过滤的方法也是比较常用的方法,也是比较多业务中有这个需求的。这里介绍两种方法

在拿到一个流后,也许不是所有的元素我们都需要。我们需要保存满足特定条件的元素,这时候就可以使用 filter方法来实现。

这里的案例表示筛选除 id1 为 1,id2 为 4 的数据,代码如下:

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(4, 5));
        List<StreamTestObject> collectList = streamTestObjects.stream()
                .filter(object -> object.getId1().equals(1) || object.getId2().equals(4))
                .collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //输出结果 collectList:[StreamTestObject(id1=1, id2=2), StreamTestObject(id1=3, id2=4)]

如果你的过滤逻辑比较复杂可以使用显示 return 写法来过滤

        List<StreamTestObject> streamTestObjects = Arrays.asList(
                new StreamTestObject(1, 2),
                new StreamTestObject(3, 4),
                new StreamTestObject(4, 5));
        List<StreamTestObject> collectList = streamTestObjects.stream()
                .filter(object -> {
                    int id1 = 1;
                    int id2 = 4;
                    return object.getId1().equals(id1) || object.getId2().equals(id2);
                }).collect(Collectors.toList());
        System.out.println("collectList:" + collectList);
        //输出结果 collectList:[StreamTestObject(id1=1, id2=2), StreamTestObject(id1=3, id2=4)]

在代码块里可以编辑自己自定义的过滤逻辑

这里要注意返回值是一个布尔值,如果为 true,则保留这项数据,不满足,则进行一项数据处理。

Stream流的其他方法

前文是 Stream 流比较常见的方法案例,它还提供了很多其他的接口来实现对应的场景,如:

  1. sorted()
    • 用途:对流中的元素进行自然排序(需实现 Comparable 接口),返回排序后的 Stream。
    • 示例:对用户列表按年龄进行排序。
  2. limit(long maxSize)
    • 用途:截断流,使其包含不超过给定数量的元素,返回截断后的 Stream。
    • 示例:只取用户列表中的前三个用户。
  3. skip(long n)
    • 用途:跳过流中的前 n 个元素,返回剩下的元素的 Stream。
    • 示例:跳过用户列表中的前两个用户,取后面的用户。
  4. forEach(Consumer<? super T> action)
    • 用途:这是大家比较熟悉的操作,在代码编写中可以省去 .Stream() 的写法。意为对流中的每个元素执行提供的操作,这是一个终结操作。
    • 示例:遍历用户列表并打印每个用户的名字。

使用Stream流的弊端

学习了Stream流 的优点之后,也需要知道随之产生的弊端有短些,这里我列举几个主要的内容

  • 性能问题
  1. 多次遍历:有时为了完成一个操作,可能需要多次遍历数据源。例如,先过滤(filter)再映射(map)最后收集(collect),这会导致数据被多次遍历。
  2. 并行流开销:虽然并行流可以加速处理过程,但它们引入了额外的线程管理开销,并且不总是能带来性能提升,尤其是在数据源较小或操作相对简单时。
  3. 懒加载导致的意外行为:Stream操作是懒加载的,这意味着它们直到需要结果时才会执行。这可能导致在调试时难以追踪问题的源头,或者在某些情况下,当流操作依赖于外部状态时,可能导致不可预测的行为。

  • 可读性和维护性

??:前面不是可读性强吗,怎么有问题了?如果嵌套太多层的操作方法,也会使得表达式的可读性降低

  1. 复杂逻辑难以追踪:对于包含多个复杂操作(如多重过滤、映射、归约等)的Stream链,其逻辑可能变得难以理解和追踪。
  2. 调试困难:由于Stream操作的延迟执行和中间操作的无状态性,调试Stream代码可能会比传统循环更加困难。

  • 错误处理
  1. 异常处理复杂:在Stream操作中处理异常(如尝试映射一个可能抛出异常的函数)比在传统循环中更复杂。Stream API没有直接支持异常处理机制,通常需要通过try-catch块或自定义函数来处理。

  • 内存消耗
  1. 中间结果存储:Stream API在执行过程中可能会创建中间结果的临时集合,尤其是在进行复杂操作时,这可能会增加内存消耗。

到这里,同学们可以多实操一下这些方法来巩固知识。文章如有遗漏或建议更改的部分欢迎佬们指出。

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

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

相关文章

Windows ipconfig命令详解,Windows查看IP地址信息

「作者简介」&#xff1a;冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础著作 《网络安全自学教程》&#xff0c;适合基础薄弱的同学系统化的学习网络安全&#xff0c;用最短的时间掌握最核心的技术。 ipconfig 1、基…

【刷题汇总--大数加法、 链表相加(二)、大数乘法】

C日常刷题积累 今日刷题汇总 - day0061、大数加法1.1、题目1.2、思路1.3、程序实现 2、 链表相加(二)2.1、题目2.2、思路2.3、程序实现 3、大数乘法3.1、题目3.2、思路3.3、程序实现 4、题目链接 今日刷题汇总 - day006 1、大数加法 1.1、题目 1.2、思路 读完题,明白大数相加…

react 项目中预防xss攻击的插件 dompurify

一、安装 $ yarn add dompurify $ yarn add --dev types/dompurify 二、使用 import DOMPurify from dompurify;// 1、处理&#xff1a; DOMPurify.sanitize(htmlContent)// 2、之后放进 dangerouslySetInnerHTML dangerouslySetInnerHTML{{ __html: cleanHTML }} 如&#…

【IT领域新生必看】 Java编程中的重载(Overloading):初学者轻松掌握的全方位指南

文章目录 引言什么是方法重载&#xff08;Overloading&#xff09;&#xff1f;方法重载的基本示例 方法重载的规则1. 参数列表必须不同示例&#xff1a; 2. 返回类型可以相同也可以不同示例&#xff1a; 3. 访问修饰符可以相同也可以不同示例&#xff1a; 4. 可以抛出不同的异…

经典双运算放大器LM358

前言 LM358双运放有几十年的历史了吧&#xff1f;通用运放&#xff0c;很常用&#xff0c;搞电路的避免不了接触运放&#xff0c;怎么选择运放&#xff0c;是工程师关心的问题吧&#xff1f; 从本文开始&#xff0c;将陆续发一些常用的运放&#xff0c;大家选型可以参考&#…

win10使用小技巧一

1. 查看电脑IP地址 步骤&#xff1a;按WinR打开运行框 → 输入cmd点确定 → 输入ipconfig回车 → 查看IP地址。 2. 解决网页文字不能复制 步骤&#xff1a;按F12 → 调试框里点击设置 → 向下滑找到 禁用 JavaScript → 勾选 → 复制文字。 3. 解决电脑不能上网 方法一&…

第10章 项目总结02:针对当前项目的面试题

1 项目概况 1.1 项目介绍 从以下几个方面进行项目介绍&#xff1a; 1、项目的背景&#xff1a;做什么业务、服务的客户群是谁、谁去运营、自研还是外包等问题。 2、项目的业务流程&#xff1a;课程发布流程、断点续传流程、视频处理流程、认证授权流程、支付流程、CI/CD流程…

SpringBoot新手快速入门系列教程:基于JPA的一个Mysql简单读写例子

现在我们来做一个简单的读写Mysql的项目 1&#xff0c;先新建一个项目&#xff0c;我们叫它“HelloJPA”并且添加依赖 2&#xff0c;引入以下依赖&#xff1a; Spring Boot DevTools (可选&#xff0c;但推荐&#xff0c;用于开发时热部署)Lombok&#xff08;可选&#xff0c…

cs224n作业4

NMT结构图&#xff1a;&#xff08;具体结构图&#xff09; LSTM基础知识 nmt_model.py&#xff1a; 参考文章&#xff1a;LSTM输出结构描述 #!/usr/bin/env python3 # -*- coding: utf-8 -*-""" CS224N 2020-21: Homework 4 nmt_model.py: NMT Model Penchen…

ElasticSearch 如何计算得分及一个不太成熟的使用

1.背景 最近在做 ES 相关东西&#xff0c;只最会在查询的时候给不同的字段设置不同的权重&#xff0c;但是得分具体怎么算的不太明白&#xff0c;花了4-5 天研究和总结了一下。这样不至于被别人问到“这个分数怎么算出来的&#xff1f;”&#xff0c;两眼一抹黑&#xff0c;不…

【C++】模板进阶--保姆级解析(什么是非类型模板参数?什么是模板的特化?模板的特化如何应用?)

目录 一、前言 二、什么是C模板&#xff1f; &#x1f4a6;泛型编程的思想 &#x1f4a6;C模板的分类 三、非类型模板参数 ⚡问题引入⚡ ⚡非类型模板参数的使用⚡ &#x1f525;非类型模板参数的定义 &#x1f525;非类型模板参数的两种类型 &#x1f52…

【Unity2D 2022:Particle System】添加命中粒子特效

一、创建粒子特效游戏物体 二、修改粒子系统属性 1. 基础属性 &#xff08;1&#xff09;修改发射粒子持续时间&#xff08;Duration&#xff09;为1s &#xff08;2&#xff09;取消勾选循环&#xff08;Looping&#xff09; &#xff08;2&#xff09;修改粒子存在时间&…

Vue表单输入绑定v-model

表单输入绑定 在前端处理表单时&#xff0c;我们常常需要将表单输入框的内容同步给Javascript中相应的变量。手动连接绑定和更改事件监听器可能会很麻&#xff0c;v-model 指令帮我们简化了这一步骤。 <template><h3>表单输入绑定</h3><hr> <inpu…

推荐Bulk Image Downloader插件下载网页中图片链接很好用

推荐&#xff1a;Bulk Image Downloader chome浏览器插件下载图片链接&#xff0c;很好用。 有个网页&#xff0c;上面放了数千的gif的电路图&#xff0c;手工下载会累瘫了不可。想找一个工具分析它的静态链接并下载&#xff0c;找了很多推荐的下载工具&#xff0c;都是不能分…

docker部署mycat,连接上面一篇的一主二从mysql

一、docker下载mycat镜像 查看安装结果 这个名称太长&#xff0c;在安装容器时不方便操作&#xff0c;设置标签为mycat docker tag longhronshens/mycat-docker mycat 二、安装容器 先安装一个&#xff0c;主要目的是获得配置文件 docker run -it -d --name mycat -p 8066:…

算法系列--分治排序|再谈快速排序|快速排序的优化|快速选择算法

前言:本文就前期学习快速排序算法的一些疑惑点进行详细解答,并且给出基础快速排序算法的优化版本 一.再谈快速排序 快速排序算法的核心是分治思想,分治策略分为以下三步: 分解:将原问题分解为若干相似,规模较小的子问题解决:如果子问题规模较小,直接解决;否则递归解决子问题合…

【JVM 的内存模型】

1. JVM内存模型 下图为JVM内存结构模型&#xff1a; 两种执行方式&#xff1a; 解释执行&#xff1a;JVM是由C语言编写的&#xff0c;其中有C解释器&#xff0c;负责先将Java语言解释翻译为C语言。缺点是经过一次JVM翻译&#xff0c;速度慢一点。JIT执行&#xff1a;JIT编译器…

java自旋锁

Java自旋锁&#xff08;Spin Lock&#xff09;是一种用于多线程同步的锁机制&#xff0c;通过反复检查某个条件&#xff08;通常是一个共享变量的状态&#xff09;而不是挂起线程来实现锁的获取。自旋锁的核心思想是让线程在尝试获取锁时保持活动状态&#xff0c;即进行“自旋”…

KBPC3506-ASEMI储能专用整流桥KBPC3506

编辑&#xff1a;ll KBPC3506-ASEMI储能专用整流桥KBPC3506 型号&#xff1a;KBPC3506 品牌&#xff1a;ASEMI 封装&#xff1a;KBPC-4 正向电流&#xff08;Id&#xff09;&#xff1a;35A 反向耐压&#xff08;VRRM&#xff09;&#xff1a;600V 正向浪涌电流&#xf…

进程控制-exec函数

让父子进程来执行不相干的操作 能够替换进程地址空间的代码.text段 执行另外的程序&#xff0c;不需要创建额外的的地址空间 当前程序中调用另外一个应用程序 指定执行目录下的程序 int execl(const char *path, const char *arg&#xff0c;/* (char *) NULL */); /* pat…