Stream API 函数式编程 - 告别for循环,代码竟能写的如此优雅?

目录

一、Stream API 函数式编程

1.1、Stream 简介

a)为什么引入 Stream?Stream 的出现就是为了让关于集合的操作更加简单:

b)Stream 的特性:

c)对stream的操作分为为两类,中间操作 和 结束操作 ,二者特点是:

d)Stream 一般不需要我们去手动 new 一个出来,而是通过以下两种方式获取:

e)Stream 的本质:

1.2、Stream 的使用

说明:案例中操作的实体类

1.2.1、forEach() 遍历

1.2.2、filter() 过滤

1.2.3、distinct() 去重

1.2.4、sorted() 排序

1.2.5、map() 映射

1.2.6、flatMap() 展开映射

1.2.7、reduce() 聚合

1.2.8、collect() 转化

1.2.9、collect() 生成 Map

1.2.10、collect() 生成字符串


一、Stream API 函数式编程


1.1、Stream 简介

a)为什么引入 Stream?Stream 的出现就是为了让关于集合的操作更加简单:

  1. 代码简洁优雅,函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
  2. 简写并发执行,Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法,透明地并行处理,你无需写任何多线程代码,极大的提高编程效率和程序可读性。

b)Stream 的特性:

  • 无存储stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
  • 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream
  • 惰式执行stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
  • 可消费性stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

c)对stream的操作分为为两类,中间操作 和 结束操作 ,二者特点是:

  1. 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。例如:concat() distinct() filter() flatMap() limit() map() peek() skip() sorted() parallel() sequential() unordered()
  2. 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。例如:allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

d)Stream 一般不需要我们去手动 new 一个出来,而是通过以下两种方式获取:

  • 调用 Collection.stream() 或者 Collection.parallelStream() 方法(并发)
  • 调用 Arrays.stream(T[] array) 方法

e)Stream 的本质:

函数式接口(内部只有一个抽象方法),同时也是 lambda 表达式的本质,因此我们不需要记住函数接口的名字.

1.2、Stream 的使用

说明:案例中操作的实体类

@Data
@AllArgsConstructor
class Student {private String name;private Integer score;private Integer grade;
}@Data
@AllArgsConstructor
class User {private String name;private Integer age;}@Builder
class UserVO {private String name;private Integer age;private String other;}

1.2.1、forEach() 遍历

a)forEach 因该都不陌生,也就是对每个元素进行遍历.

    List.of("a", "ab", "abc", "abcd").stream().forEach(str -> System.out.println(str));

如果只是为了打印,可以如下简写.

    List.of("a", "ab", "abc", "abcd").stream().forEach(System.out::println);

b)两个冒号是啥意思?

例如 String::length 的语法形式叫做方法引用(method references),这种语法用来替代某些特定形式Lambda表达式。如果Lambda表达式的全部内容就是调用一个已有的方法,那么可以用方法引用来替代Lambda表达式。方法引用可以细分为四类:

方法引用类别举例
引用静态方法Integer::sum
引用某个对象的方法list::add
引用某个类的方法String::length
引用构造方法HashMap::new

Ps:后面会引入更多这种的例子

1.2.2、filter() 过滤

用来过滤掉容器中的一些数据,返回一个只符合条件的 Stream.

例如返回容器中长度大于 2 的字符串

    List.of("a", "ab", "abc", "abcd").stream().filter(str -> str.length() > 2).forEach(System.out::println);

上述代码就会输出:abc  abcd

1.2.3、distinct() 去重

distinct 的作用就是返回一个去重之后的 Stream

    List.of("a", "ab", "abc", "abc").stream().distinct().forEach(System.out::println);

上述代码就会输出:a ab abc

1.2.4、sorted() 排序

sorted 可以用来排序.  排序的方式有以下两种:

自然排序:Stream<T> sorted()

自定义比较器排序:Stream<T> sorted(Comparator<? super T> comparator)

    List<Integer> nums = List.of(4, 1, 3, 2);//a) 输出 1 2 3 4nums.stream().sorted().forEach(num -> System.out.print(num + " "));//b) 输出 4 3 2 1nums.stream().sorted((n1, n2) -> n2 - n1).forEach(num -> System.out.print(num + " "));

1.2.5、map() 映射

通俗来讲,就是对 stream 中的每一个元素按照某种操作之后进行转化,转换前后元素个数不会变,类型取决于转换之后的类型.

a)例如大小写转化

    //a) 小写转大写List.of("a", "ab", "abc", "abcd").stream().map(str -> str.toUpperCase()).forEach(str -> System.out.println(str + " "));

b)例如从数据库拿到一组数据,转化成所需要的 DO 或者 VO 类(以下只是模拟)

  public void test5() {List<UserVO> list = List.of(new User("aaa", 20),new User("bbb", 19),new User("ccc", 31)) //从数据库中拿到数据.stream().map(this::map).toList();}public UserVO map(User user) {return UserVO.builder().name(user.getName()).age(user.getAge()).build();}

上述代码中,我们把从数据库中拿到的 List<User>(模拟数据库数据),转化成了 List<UserVO>.

1.2.6、flatMap() 展开映射

通俗的讲 flatMap() 的作用就相当于把原 stream 中的所有集合中的元素都拿出来之后组和到一个 Stream 中展开. 转换前后元素的个数和类型都可能会改变。

例如Stream中两个 List 元素,通过 flatMap 将这两个元素全部展开了.

    //将两个 List 变成了一个 ListStream.of(List.of(1, 2, 3), List.of(4, 5, 6)).flatMap(list -> list.stream()).forEach(System.out::println); //输出 1 2 3 4 5 6

1.2.7、reduce() 聚合

reduce操作可以实现从一组元素中生成一个值,sum()max()min()count()等都是reduce操作,将他们单独设为函数只是因为常用。reduce()的方法定义有三种重写形式:

  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • T reduce(T identity, BinaryOperator<T> accumulator)
  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

虽然函数定义越来越长,但语义不曾改变,多的参数只是为了指明初始值(参数identity),或者是指定并行执行时多个部分结果的合并方式(参数combiner)。

reduce()最常用的场景就是从一堆值中生成一个值。

例如,找出长度最长的单词、求单词长度之和.

    //1.找出长度最长的单词Optional<String> reduce = List.of("a", "ab", "abc", "abcd").stream().reduce((s1, s2) -> s1.length() > s2.length() ? s1 : s2);System.out.println(reduce.get());//2.求单词长度之和Integer num = List.of("a", "ab", "abc", "abcd").stream().reduce(0, //起始大小(sum, str) -> sum + str.length(), //组合方法(a, b) -> a + b //并行执行才会使用到(其他时候不会执行));System.out.println(num);

1.2.8、collect() 转化

collect 用来将一个 stream 执行一个特定动作,最后生成一个集合.

而这个特定动作就是 Collectors 收集器提供的方法.

例如将 Stream 转化成 List、Set、Map.

    Stream<String> stream = Stream.of("a", "ab", "abc", "abcd");//1.将 Stream 转换成 ListList<String> collect = stream.collect(Collectors.toList());//2.将 Stream 转换成 SetSet<String> collect1 = stream.collect(Collectors.toSet());//3.将 Stream 转换成 Map.   Collectors.toMap(如何生成 key, 如何生成 value)Map<String, Integer> collect2 = stream.collect(Collectors.toMap(Function.identity(), str -> str.length()));//方法引用Map<String, Integer> collect3 = stream.collect(Collectors.toMap(Function.identity(), String::length));

 有人可能疑惑了:Function.identity() 是什么?

Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如 p -> p 形式的Lambda表达式.  那为什么不用 p -> p 这种方式直接表示呢? 这是因为在Java 7及之前要想在定义好的接口中加入新的抽象方法是很困难甚至不可能的,因为所有实现了该接口的类都要重新实现......

上述代码能满足绝大多数需求,但是如果我们要去指定生成一个接口具体的类型,就可以按如下方式,例如 ArrayList、HashSet:

    Stream<String> stream = Stream.of("a", "ab", "abc", "abcd");//将 Stream 转换成 ArrayListArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));//将 Stream 转换成 HashSetHashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));

1.2.9、collect() 生成 Map

使用 collect 生成 Map 处理前面讲到了的最基本的方式 Collectors.toMap() 收集器 生成 Map 以外,还有两种方式~  这里我把三种方式都罗列出来:

  1. 使用 Collectors.toMap()生成的收集器,用户需要指定如何生成 Map 的 key 和 value
  2. 使用 Collectors.partitioningBy()生成的收集器,对元素进行二分区操作时用到。
  3. 使用 Collectors.groupingBy()生成的收集器,对元素做group操作时用到(类似 sql 重点 group by 操作)。

a)先来看第一种,toMap 的方式最直接,例如将 学生列表 转化成 <学生,成绩> 的 Map

    Stream<Student> studentStream = Stream.of(new Student("aaa", 80, 1),new Student("bbb", 78, 2),new Student("ccc", 96, 3));//用 Map 统计学生成绩Map<Student, Integer> map = studentStream.collect(Collectors.toMap(Function.identity(), Student::getScore));

b)partitioningBy() 就是按照 满足/不满足 的逻辑进行划分. key 就是 boolean 类型,true 和 false 就分别表示了 满足 和 不满足 的逻辑

例如将学生按照分数是否及格来划分:

    Stream<Student> studentStream = Stream.of(new Student("aaa", 80, 1),new Student("bbb", 58, 2),new Student("ccc", 96, 3));//统计成绩合格和不合格的学生Map<Boolean, List<Student>> map = studentStream.collect(Collectors.partitioningBy(stu -> stu.getScore() >= 60));

上述代码中,凡事 key 为 true 的,对应对 value 就是 score >= 60 的学生

c)groupingBy() 就类似于 sql 语句中 group by 分组.

例如将 学生 按照班级进行分组,那么 key 就是班级,value 就是 学生列表

    Stream<Student> studentStream = Stream.of(new Student("aaa", 80, 1),new Student("bbb", 58, 2),new Student("ccc", 96, 2),new Student("ddd", 67, 1));//按照 grade 年级进行分组Map<Integer, List<Student>> map = studentStream.collect(Collectors.groupingBy(Student::getGrade));System.out.println(map);

例如我们还想要将 学生 按照班级分组后,再展示出每个班的学生数量(或者是 最大值、最小值、求和、平均值这种计算),如下代码

    Stream<Student> studentStream = Stream.of(new Student("aaa", 80, 1),new Student("bbb", 58, 2),new Student("ccc", 96, 2),new Student("ddd", 67, 1));//先按照 grade 年级分组,再统计每个年级的人数Map<Integer, Long> map = studentStream.collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));

Ps:这种先将元素分组的收集器叫做上游收集器,之后执行其他运算的收集器叫做下游收集器(downstream Collector)。

下游收集器还可以包含更下游的收集器,例如我们将学生按照年级分组后,不想直接得到一个学生列表,而是一个学生的姓名列表,就可以通过如下方式得到.

    Stream<Student> studentStream = Stream.of(new Student("aaa", 80, 1),new Student("bbb", 58, 2),new Student("ccc", 96, 2),new Student("ddd", 67, 1));//先按照 grade 年级分组,再统计每个年纪的人名字Map<Integer, List<String>> collect = studentStream.collect(Collectors.groupingBy(Student::getGrade, //key: 按照名字分组Collectors.mapping(Student::getName, //value: 名字 (下游收集器)Collectors.toList() // 用 List 来收集这些 value (更下游的收集器))));

1.2.10、collect() 生成字符串

Collectors.joining 有三种重载方式,来拼接字符串,如下:

    Stream<String> stream = Stream.of("what", "do", "you", "meaning");
//    String collect1 = stream.collect(Collectors.joining(",")); // whatdoyoumeaning
//    String collect2 = stream.collect(Collectors.joining(",")); // what,do,you,meaningString collect3 = stream.collect(Collectors.joining(",", "{", "}")); // {what,do,you,meaning}

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

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

相关文章

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-3 textarea

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>textarea</title> </head><body> <h2>多行文本框:</h2> <!--textarea&#xff08;文本域&#xff09;cols(列) rows(行)--> …

Template Engine-06-模板引擎 Handlebars 入门介绍

拓展阅读 java 表达式引擎 logstash 日志加工处理-08-表达式执行引擎 AviatorScriptMVELOGNLSpELJEXLJUELJanino QLExpress 阿里表达式引擎系统学习 什么是 Handlebars&#xff1f; Handlebars 是一种简单的模板语言。 它使用模板和输入对象生成 HTML 或其他文本格式。Ha…

go语言(一)----声明常量

package mainimport ("fmt""time" )func main() {fmt.Print("hello go!")time.Sleep(1 * time.Second)}运行后&#xff0c;结果如下&#xff1a; 1、golang表达式中&#xff0c;加&#xff1b;和不加&#xff1b;都可以 2、函数的{和函数名一…

【Qt之模型视图】1. 模型和视图架构

1. 模型/视图架构是什么及有什么用 MVC&#xff08;Model-View-Control&#xff09;是一种源自Smalltalk的设计模式&#xff0c;通常用于构建用户界面。 MVC由三种类型的对象组成。模型是应用对象&#xff0c;用来表示数据&#xff1b;视图是模型的用户界面&#xff0c;用来显…

【征服redis5】redis的Redisson客户端

目录 1 Redisson介绍 2. 与其他Java Redis客户端的比较 3.基本的配置与连接池 3.1 依赖和SDK 3.2 配置内容解析 4 实战案例&#xff1a;优雅的让Hash的某个Field过期 5 Redisson的强大功能 1 Redisson介绍 Redisson 最初由 GitHub 用户 “mrniko” 创建&#xff0c;并在…

瑞_Java开发手册_(七)设计规约

文章目录 设计规约的意义设计规约 &#x1f64a;前言&#xff1a;本文章为瑞_系列专栏之《Java开发手册》的设计规约篇。由于博主是从阿里的《Java开发手册》学习到Java的编程规约&#xff0c;所以本系列专栏主要以这本书进行讲解和拓展&#xff0c;有需要的小伙伴可以点击链接…

Java数据结构实现数组(配套习题)

数据结构 数组 一组相同数据类型的集合 特点 数组在内存中是连续分配的创建时要指明数组的大小数组名代表首地址,索引从0开始,到数组的长度-1数组一旦创建好,大小不可以改变使用索引 获取索引位置的值 arr[index]修改 arr[index] val删除 (假删除)遍历,将数组中的元素,依次…

在全志T113-i平台上实现H.265视频解码步骤详解

H.265&#xff0c;也被称为HEVC(HighEfficiency Video Coding)&#xff0c;作为H.264的继任者&#xff0c;提供了更好的视频压缩和更高的视频质。H.265通过引入更多先进的编码技术&#xff0c;如更强大的运动估计和更高效的变换编码&#xff0c;对比H.264进行了改进。这些改进使…

【latex】参考文献排版前移,在最前面引用\usepackage{url}

【LaTeX】参考文献排版前移&#xff0c;在最前面引用\usepackage{url} 写在最前面完整解决步骤请教申申latex编译报错解决方案 写在最前面 参考文献从21开始排版前移了 解决方案&#xff1a;在最前面加一行 \usepackage{url}完整解决步骤 请教申申 申申yyds&#xff01;&am…

Java NIO (一)简介

1 NIO简介 在1.4版本之前&#xff0c;Java NIO类库是阻塞IO&#xff0c;从1.4版本开始&#xff0c;引进了新的异步IO库&#xff0c;被称为Java New IO类库&#xff0c;简称为Java NIO。New IO类库的目的 就是要让Java支持非阻塞IO。 Java NIO类库包含三个核心组件&#xff1a; …

Python 算术运算符:解码数字世界的算术密码

算术运算是计算机编程中最基本和常见的运算之一。在 Python 中&#xff0c;算术运算符提供了一组功能强大的操作符&#xff0c;使得我们能够对数字进行加减乘除等各种数学计算。本文将深入探讨 Python 中的算术运算符&#xff0c;包括常见的算术运算符、使用注意事项以及在实际…

线性回归理论+实战

线性回归 什么是线性回归 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation (d2l.ai) 模型 损失函数 模型拟合&#xff08;fit&#xff09;数据之前&#xff0c;我们需要确定一个拟合程度的度量。 损失函数&#xff08;loss function&#xff09;能够量化目标的实际值…

导入失败,报错:“too many filtered rows xxx, “ErrorURL“:“

一、问题&#xff1a; 注&#xff1a;前面能正常写入&#xff0c;突然就报错&#xff0c;导入失败&#xff0c;报错&#xff1a;“too many filtered rows xxx, "ErrorURL":" {"TxnId":769494,"Label":"datax_doris_writer_bf176078-…

物联网中的通信技术

阅读引言&#xff1a; 本文主要大致为大家带来物联网中的常见的通信方式的知识梳理。 目录 一、概述 二、无线通信技术 1.物联网电子标签 RFID 1.1 RFID 概念 1.2 RFID 系统组成 2.WI-FI技术 3.UWB技术 4.ZigBee技术 5.NFC技术 6.蓝牙技术 7.EnOcean技术 一、概述 物…

【服务器数据恢复】服务器迁移数据时lun数据丢失的数据恢复案例

服务器数据恢复环境&服务器故障&#xff1a; 一台安装Windows操作系统的服务器。工作人员在迁移该服务器中数据时突然无法读取数据&#xff0c;服务器管理界面出现报错。经过检查发现服务器中一个lun的数据丢失。 服务器数据恢复过程&#xff1a; 1、将故障服务器中所有磁盘…

SpringBoot+MybatisPlus+dynamic-datasources实现连接Postgresql和mysql多数据源

场景 dynamic-datasource-spring-boot-starter实现动态数据源Mysql和Sqlserver&#xff1a; dynamic-datasource-spring-boot-starter实现动态数据源Mysql和Sqlserver_dynamic-datasource-spring-boot-starter mysql sqlse-CSDN博客 SpringBoot中整合MybatisPlus快速实现Mys…

利用docker的LNMP

目录 服务器环境 任务需求 服务搭建 Nginx Mysql Php 启动 wordpress 服务 服务器环境 容器 操作系统 IP地址 主要软件 nginx CentOS 7 172.20.0.10 Docker-Nginx mysql CentOS 7 172.20.0.20 Docker-Mysql php CentOS 7 172.2…

详解ISIS动态路由协议

华子目录 前言应用场景历史起源ISIS路由计算过程ISIS的地址结构ISIS路由器分类ISIS邻居关系的建立P2PMA ISIS中的DIS与OSPF中DR的对比链路状态信息的交互ISIS的最短路径优先算法&#xff08;SPF&#xff09;ISIS区域划分ISIS区域间路由访问原理ISIS与OSPF的不同ISIS与OSPF的术语…

Asp .Net Core 系列:集成 Ocelot+Consul实现网关、服务注册、服务发现

什么是Ocelot? Ocelot是一个开源的ASP.NET Core微服务网关&#xff0c;它提供了API网关所需的所有功能&#xff0c;如路由、认证、限流、监控等。 Ocelot是一个简单、灵活且功能强大的API网关&#xff0c;它可以与现有的服务集成&#xff0c;并帮助您保护、监控和扩展您的微…

如何安装下载激活MathType?2024最新免费MathType许可证

第一步&#xff1a;请先从这里下载安装MathType&#xff1a; 第二步&#xff1a;下载完成后&#xff0c;双击下载的MathType Desktop安装程序文件。 在Mac上&#xff0c;这将在单独的窗口中打开它&#xff0c;因此在该窗口中双击“ MathType Desktop Installer…”以运行安装…