Java8中Stream流API最佳实践Lambda表达式使用示例

news2024/12/26 11:11:17

文章目录

    • 一、创建流
    • 二、中间操作和收集操作
      • 筛选 filter
      • 去重distinct
      • 截取
      • 跳过
      • 映射
      • 合并多个流
      • 是否匹配任一元素:anyMatch
      • 是否匹配所有元素:allMatch
      • 是否未匹配所有元素:noneMatch
      • 获取任一元素findAny
      • 获取第一个元素findFirst
      • 归约
      • 数值流的使用
      • 中间操作和收集操作总结
      • Collector 收集
      • 归约
      • 一般性归约
      • 汇总
      • 分组
      • 多级分组
      • 转换类型
      • 数据分区
      • 并行流

在这里插入图片描述
stream在使用中主要分为以上图示阶段,接下来将详细展示java8中stream api的具体的实践使用案例。

一、创建流

在使用流之前,首先需要拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的流对象。数据源可以有多种形式:

1. 集合

这种数据源较为常用,通过stream()方法即可获取流对象:

List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();

2. 数组

通过Arrays类提供的静态函数stream()获取数组的流对象:

String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);

3. 值

直接将几个值变成流对象:

Stream<String> stream = Stream.of("chaimm","peter","john");

4. 文件

try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())){
    //可对lines做一些操作
}catch(IOException e){
}

5. iterator

创建无限流

Stream.iterate(0, n -> n + 2)
      .limit(10)
      .forEach(System.out::println);

PS:Java7简化了IO操作,把打开IO操作放在try后的括号中即可省略关闭IO的代码。

二、中间操作和收集操作

筛选 filter

filter 函数接收一个Lambda表达式作为参数,该表达式返回boolean,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。
如,筛选出所有学生:

List<Person> result = list.stream()
                    .filter(Person::isStudent)
                    .collect(toList());

去重distinct

去掉重复的结果:

List<Person> result = list.stream()
                    .distinct()
                    .collect(toList());

截取

截取流的前N个元素:

List<Person> result = list.stream()
                    .limit(3)
                    .collect(toList());

跳过

跳过流的前n个元素:

List<Person> result = list.stream()
                    .skip(3)
                    .collect(toList());

映射

对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如,获取每个人的姓名(实则是将Perosn类型转换成String类型):

List<Person> result = list.stream()
                    .map(Person::getName)
                    .collect(toList());

合并多个流

例:列出List中各不相同的单词,List集合如下:

List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");

思路如下:

首先将list变成流:

list.stream();

按空格分词:

list.stream()
            .map(line->line.split(" "));

分完词之后,每个元素变成了一个String[]数组。

将每个 String[] 变成流:

list.stream()
            .map(line->line.split(" "))
            .map(Arrays::stream)

此时一个大流里面包含了一个个小流,我们需要将这些小流合并成一个流。

将小流合并成一个大流:用 flatMap 替换刚才的 map

list.stream()
    .map(line->line.split(" "))
    .flatMap(Arrays::stream)

去重

list.stream()
    .map(line->line.split(" "))
    .flatMap(Arrays::stream)
    .distinct()
    .collect(toList());

是否匹配任一元素:anyMatch

anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断list中是否有学生:

boolean result = list.stream()
            .anyMatch(Person::isStudent);

是否匹配所有元素:allMatch

allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
如,判断是否所有人都是学生:

boolean result = list.stream()
            .allMatch(Person::isStudent);

是否未匹配所有元素:noneMatch

noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:

boolean result = list.stream()
            .noneMatch(Person::isStudent);

获取任一元素findAny

findAny能够从流中随便选一个元素出来,它返回一个Optional类型的元素。

Optional<Person> person = list.stream().findAny();

获取第一个元素findFirst

Optional<Person> person = list.stream().findFirst();

归约

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。

在流中,reduce函数能实现归约。
reduce函数接收两个参数:

  1. 初始值
  2. 进行归约操作的Lambda表达式

元素求和:自定义Lambda表达式实现求和

例:计算所有人的年龄总和

@Test
public void contextLoads() {
   List<Person> list = new ArrayList<>();
   list.add(new Person().setAge(20));
   list.add(new Person().setAge(25));
   int age = list.stream().map(Person::getAge).reduce(0, Integer::sum);
   System.out.println(age);
}

@Data
@Accessors(chain = true)
class Person {
   private int age;
}
  1. reduce的第一个参数表示初试值为0;
  2. reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。

元素求和:使用Integer.sum函数求和

上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:

int age = list.stream().reduce(0, Integer::sum);

Integer类还提供了 minmax 等一系列数值操作,当流中元素为数值类型时可以直接使用。

数值流的使用

采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。
当流操作为纯数值操作时,使用数值流能获得较高的效率。

将普通流转换成数值流

StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。
如,将Person中的age转换成数值流:

IntStream stream = list.stream().mapToInt(Person::getAge);

数值计算

每种数值流都提供了数值计算函数,如max、min、sum等。如,找出最大的年龄:

OptionalInt maxAge = list.stream()
                                .mapToInt(Person::getAge)
                                .max();

由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong

中间操作和收集操作总结

操作类型返回类型使用的类型/函数式接口函数描述符
filter中间Stream<T>Predicate<T>T -> boolean
distinct中间Stream<T>
skip中间Stream<T>long
map中间Stream<R>Function<T, R>T -> R
flatMap中间Stream<R>Function<T, Stream<R>>T -> Stream<R>
limit中间Stream<T>long
sorted中间Stream<T>Comparator<T>(T, T) -> int
anyMatch终端booleanPredicate<T>T -> boolean
noneMatch终端booleanPredicate<T>T -> boolean
allMatch终端booleanPredicate<T>T -> boolean
findAny终端Optional<T>
findFirst终端Optional<T>
forEach终端voidConsumer<T>T -> void
collect终端RCollector<T, A, R>
reduce终端Optional<T>BinaryOperator<T>(T, T) -> T
count终端long

Collector 收集

收集器用来将经过筛选、映射的流进行最后的整理,可以使得最后的结果以不同的形式展现。
collect 方法即为收集器,它接收 Collector 接口的实现作为具体收集器的收集方法。
Collector 接口提供了很多默认实现的方法,我们可以直接使用它们格式化流的结果;也可以自定义 Collector 接口的实现,从而定制自己的收集器。

归约

流由一个个元素组成,归约就是将一个个元素“折叠”成一个值,如求和、求最值、求平均值都是归约操作。

一般性归约

若你需要自定义一个归约操作,那么需要使用 Collectors.reducing 函数,该函数接收三个参数:

  • 第一个参数为归约的初始值
  • 第二个参数为归约操作进行的字段
  • 第三个参数为归约操作的过程

汇总

Collectors类专门为汇总提供了一个工厂方法:Collectors.summingInt
它可接受一 个把对象映射为求和所需int的函数,并返回一个收集器;该收集器在传递给普通的 collect 方法后即执行我们需要的汇总操作。

分组

数据分组是一种更自然的分割数据操作,分组就是将流中的元素按照指定类别进行划分,类似于SQL语句中的 GROUPBY

多级分组

多级分组可以支持在完成一次分组后,分别对每个小组再进行分组。
使用具有两个参数的 groupingBy 重载方法即可实现多级分组。

  • 第一个参数:一级分组的条件
  • 第二个参数:一个新的 groupingBy 函数,该函数包含二级分组的条件

Collectors 类的静态工厂方法

工厂方法返回类型用途示例
toListList<T>把流中所有项目收集到一个 ListList<Project> projects = projectStream.collect(toList());
toSetSet<T>把流中所有项目收集到一个 Set,删除重复项Set<Project> projects = projectStream.collect(toSet());
toCollectionCollection<T>把流中所有项目收集到给定的供应源创建的集合Collection<Project> projects = projectStream.collect(toCollection(), ArrayList::new);
countingLong计算流中元素的个数long howManyProjects = projectStream.collect(counting());
summingIntInteger对流中项目的一个整数属性求和int totalStars = projectStream.collect(summingInt(Project::getStars));
averagingIntDouble计算流中项目 Integer 属性的平均值double avgStars = projectStream.collect(averagingInt(Project::getStars));
summarizingIntIntSummaryStatistics收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值IntSummaryStatistics projectStatistics = projectStream.collect(summarizingInt(Project::getStars));
joiningString连接对流中每个项目调用 toString 方法所生成的字符串String shortProject = projectStream.map(Project::getName).collect(joining(", "));
maxByOptional<T>按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty()Optional<Project> fattest = projectStream.collect(maxBy(comparingInt(Project::getStars)));
minByOptional<T>按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty()Optional<Project> fattest = projectStream.collect(minBy(comparingInt(Project::getStars)));
reducing归约操作产生的类型从一个作为累加器的初始值开始,利用 BinaryOperator 与流中的元素逐个结合,从而将流归约为单个值int totalStars = projectStream.collect(reducing(0, Project::getStars, Integer::sum));
collectingAndThen转换函数返回的类型包含另一个收集器,对其结果应用转换函数int howManyProjects = projectStream.collect(collectingAndThen(toList(), List::size));
groupingByMap<K, List<T>>根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键Map<String,List<Project>> projectByLanguage = projectStream.collect(groupingBy(Project::getLanguage));
partitioningByMap<Boolean,List<T>>根据对流中每个项目应用断言的结果来对项目进行分区Map<Boolean,List<Project>> vegetarianDishes = projectStream.collect(partitioningBy(Project::isVegetarian));

转换类型

有一些收集器可以生成其他集合。比如前面已经见过的 toList,生成了 java.util.List 类的实例。
还有 toSettoCollection,分别生成 SetCollection 类的实例。
到目前为止, 我已经讲了很多流上的链式操作,但总有一些时候,需要最终生成一个集合——比如:

  • 已有代码是为集合编写的,因此需要将流转换成集合传入;
  • 在集合上进行一系列链式操作后,最终希望生成一个值;
  • 写单元测试时,需要对某个具体的集合做断言。

使用 toCollection,用定制的集合收集元素

stream.collect(toCollection(TreeSet::new));

还可以利用收集器让流生成一个值。 maxByminBy 允许用户按某种特定的顺序生成一个值。

数据分区

分区是分组的特殊情况:由一个断言(返回一个布尔值的函数)作为分类函数,它称分区函数。
分区函数返回一个布尔值,这意味着得到的分组 Map 的键类型是 Boolean,于是它最多可以分为两组: true是一组,false是一组。

分区的好处在于保留了分区函数返回true或false的两套流元素列表。

并行流

并行流就是一个把内容分成多个数据块,并用不不同的线程分别处理每个数据块的流。最后合并每个数据块的计算结果。

将一个顺序执行的流转变成一个并发的流只要调用 parallel() 方法

public static long parallelSum(long n){
    return Stream.iterate(1L, i -> i +1).limit(n).parallel().reduce(0L,Long::sum);
}

将一个并发流转成顺序的流只要调用 sequential() 方法

stream.parallel().filter(...).sequential().map(...).parallel().reduce();

这两个方法可以多次调用,只有最后一个调用决定这个流是顺序的还是并发的。

并发流使用的默认线程数等于你机器的处理器核心数。

通过这个方法可以修改这个值,这是全局属性。

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "12");

并非使用多线程并行流处理数据的性能一定高于单线程顺序流的性能,因为性能受到多种因素的影响。
如何高效使用并发流的一些建议:

  1. 如果不确定, 就自己测试。
  2. 尽量使用基本类型的流 IntStream, LongStream, DoubleStream
  3. 有些操作使用并发流的性能会比顺序流的性能更差,比如limit,findFirst,依赖元素顺序的操作在并发流中是极其消耗性能的。findAny的性能就会好很多,应为不依赖顺序。
  4. 考虑流中计算的性能(Q)和操作的性能(N)的对比, Q表示单个处理所需的时间,N表示需要处理的数量,如果Q的值越大, 使用并发流的性能就会越高。
  5. 数据量不大时使用并发流,性能得不到提升。
  6. 考虑数据结构:并发流需要对数据进行分解,不同的数据结构被分解的性能时不一样的。

流的数据源和可分解性

可分解性
ArrayList非常好
LinkedList
IntStream.range非常好
Stream.iterate
HashSet
TreeSet

流的特性以及中间操作对流的修改都会对数据对分解性能造成影响。 比如固定大小的流在任务分解的时候就可以平均分配,但是如果有filter操作,那么流就不能预先知道在这个操作后还会剩余多少元素。

考虑终端操作的性能:如果终端操作在合并并发流的计算结果时的性能消耗太大,那么使用并发流提升的性能就会得不偿失。

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

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

相关文章

Linux下的多线程编程:原理、工具及应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Flower of Life—陽花 0:34━━━━━━️&#x1f49f;──────── 4:46 &#x1f504; ◀️ ⏸ ▶️ ☰ …

【C++ RB树】

文章目录 红黑树红黑树的概念红黑树的性质红黑树节点的定义红黑树的插入代码实现总结 红黑树 AVL树是一颗绝对平衡的二叉搜索树&#xff0c;要求每个节点的左右高度差的绝对值不超过1&#xff0c;这样保证查询时的高效时间复杂度O( l o g 2 N ) log_2 N) log2​N)&#xff0c;…

钉钉小程序 - - - - - 如何通过一个链接打开小程序内的指定页面

方式1 钉钉小程序 scheme dingtalk://dingtalkclient/action/open_mini_app?miniAppId123&pagepages%2Findex%2Findex%3Fx%3D%25E4%25B8%25AD%25E6%2596%2587 方式2 https://applink.dingtalk.com/action/open_mini_app?type2&miniAppIdminiAppId&corpIdcorpId&…

MySQL行锁核心知识介绍

MySQL的行锁是数据库中用于控制并发访问的一种机制。它允许在数据库的行级别上实现锁定&#xff0c;从而允许多个事务同时修改不同行的数据&#xff0c;而不会相互干扰。这种锁机制可以提高数据库的并发性能&#xff0c;减少锁争用&#xff0c;提高事务的吞吐量。在本教程中&am…

一周学会Django5 Python Web开发-Jinja3模版引擎-安装与配置

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计35条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

.Net Core 中间件验签

文章目录 为什么是用中间件而不是筛选器&#xff1f;代码实现技术要点context.Request.EnableBuffering()指针问题 小结 为什么是用中间件而不是筛选器&#xff1f; 为什么要用中间件验签&#xff0c;而不是筛选器去验签? 1、根据上图我们可以看到&#xff0c;中间件在筛选器之…

Selenium控制已运行的Edge和Chrome浏览器——在线控制 | 人机交互(详细启动步骤和bug记录)

文章目录 前期准备1. 浏览器开启远程控制指令&#xff08;1&#xff09;Edge&#xff08;2&#xff09;Chrome 2. 执行python代码&#xff08;1&#xff09;先启动浏览器后执行代码&#xff08;2&#xff09;通过代码启动浏览器&#xff08;3&#xff09;Bug问题记录1&#xff…

前端和后端权限控制【笔记】

前端权限设置【笔记】 前言版权推荐前端权限设置需求效果实现资源 后端权限控制1.给所有前端请求都携带token2.添加拦截器3.配置到WebMvcConfiguration4.更多的权限验证 最后 前言 2024-3-15 18:27:26 以下内容源自《【笔记】》 仅供学习交流使用 版权 禁止其他平台发布时删…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:FormLink)

提供静态卡片交互组件&#xff0c;用于静态卡片内部和提供方应用间的交互&#xff0c;当前支持router、message和call三种类型的事件。 说明&#xff1a; 该组件从API Version 10开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 该组件仅可…

Gitlab CI/CD 自动化打包部署前端(vue)项目

一、虚拟机安装 1.vmware下载 2.镜像下载 3.Ubuntu 4.新建虚拟机 一直点下一步&#xff0c;直到点击完成。 5.分配镜像 二、Gitlab CI/CD 自动化部署项目 1.配置GitLab CI/CD&#xff1a; A.在你的Vue.js项目中&#xff0c;创建一个名为.gitlab-ci.yml的文件&#xff0…

Javaweb--CSS

一&#xff1a;概述 CSS &#xff08;Cascading Style Sheet&#xff08;层叠样式表&#xff09;&#xff09;是一门语言&#xff0c;用于控制网页表现。 W3C标准规定了网页是由以下组成&#xff1a; 结构&#xff1a;HTML 表现&#xff1a;CSS 行为&#xff1a;JavaScrip…

【计算机网络】UDP/TCP 协议

TCP 协议 一、传输层1. 再谈端口号2. 端口号范围划分3. 进程和端口号4. netstat5. pidof 二、UDP 协议1. UDP 协议端格式(报文)2. UDP 的特点3. 面向数据报4. UDP 的缓冲区 三、TCP 协议1. 认识 TCP2. TCP 协议段格式&#xff08;1&#xff09;4 位首部长度&#xff08;2&#…

(三)丶RabbitMQ的四种类型交换机

前言&#xff1a;四大交换机工作原理及实战应用 1.交换机的概念 交换机可以理解成具有路由表的路由程序&#xff0c;仅此而已。每个消息都有一个称为路由键&#xff08;routing key&#xff09;的属性&#xff0c;就是一个简单的字符串。最新版本的RabbitMQ有四种交换机类型&a…

用真值表、逻辑表达式和卡诺图来表示数字电路中的逻辑关系

真值表&#xff08;Truth Table&#xff09; 真值表是一种直观的方式&#xff0c;通过列出所有可能的输入值及其对应的输出值来表示逻辑关系。以下是使用真值表表示逻辑关系的步骤&#xff1a; 1. 确定输入变量&#xff1a;列出数字电路中所有的输入变量。 2. 定…

CART决策树暴力生成风控规则(Python代码)

上一篇我们介绍了决策树节点信息更新的方法风控规则的决策树可视化&#xff08;升级版&#xff09;&#xff0c;以辅助我们制定风控规则&#xff0c;可视化的方法比较直观&#xff0c;适合做报告展示&#xff0c;但分析的时候效果没那么高。 本篇我们介绍一种通过决策树自动挖…

基于openresty构建运维工具链实践

本文字数&#xff1a;4591字 预计阅读时间&#xff1a;25 01 导读 如今OpenResty已广泛被各个互联网公司在实际生产环境中应用&#xff0c;在保留Nginx高并发、高稳定等特性基础上&#xff0c;通过嵌入Lua来提升在负载均衡层的开发效率并保证其高性能。本文主要介绍接口鉴权、流…

【Java】容器|Set、List、Map及常用API

目录 一、概述 二、List 1、List的常用API 2、ArrayList 3、List遍历 三、Set 1、Set的常用方法: 2、HashSet 3、遍历集合&#xff1a; 四、Map 1、Map常用API 2、HashMap 3、遍历Map 五、迭代器 一、概述 在Java中所有的容器都属于Collection接口下的内容 1…

D-泛醇(右泛醇)应用领域广泛 我国市场参与者众多

D-泛醇&#xff08;右泛醇&#xff09;应用领域广泛 我国市场参与者众多 D-泛醇又称右泛醇、原维生素B5&#xff0c;化学式为C9H19NO4&#xff0c;为泛醇的右旋异构体。D-泛醇外观呈无色粘稠或透明液体&#xff0c;微含臭味&#xff0c;可溶于甲醇、乙醇、水和丙二醇。D-泛醇综…

react native 实现自定义底部导航与路由文件配置

首先先把需要的一些库引入 yarn install react-navigation/native yarn install react-native-screens react-native-safe-area-context yarn install react-navigation/native-stack yarn add react-navigation/bottom-tabs 创建路由文件及四个底部导航页面 router文件下的bot…

MATLAB:一些杂例

a 2; b 5; x 0:pi/40:pi/2; %增量为pi/40 y b*exp(-a*x).*sin(b*x).*(0.012*x.^4-0.15*x.^30.075*x.^22.5*x); %点乘的意义 z y.^2; %点乘的意义 w(:,1) x; %组成w&#xff0c;第一列为x w(:,2) y; %组成w&#xff0c;第二列为y w(:,3) z; %组成w&#xff0c;第三列为z…