报名参加CloudWeGo黑客松,奖金直推双丰收! 了解详情
写点什么

简洁方便的集合处理 Java 8 stream 流

  • 2020-02-10
  • 本文字数:5383 字

    阅读完需:约 18 分钟

简洁方便的集合处理 Java 8 stream流

背景

java 8 已经发行好几年了,前段时间 java 12 也已经问世,但平时的工作中,很多项目的环境还停留在 java1.7 中。而且 java8 的很多新特性都是革命性的,比如各种集合的优化、lambda 表达式等,所以我们还是要去了解 java8 的魅力。


今天我们来学习 java8 的 Stream,并不需要理论基础,直接可以上手去用。


我接触 stream 的原因,是我要搞一个用户收入消费的数据分析。起初的统计筛选分组都是打算用 sql 语言直接从 mysql 里得到结果来展现的。但在操作中我们发现这样频繁地访问数据库,性能会受到很大的影响,分析速度会很慢。所以我们希望能通过访问一次数据库就拿到所有数据,然后放到内存中去进行数据分析统计过滤。


接着,我看了 stream 的 API,发现这就是我想要的。

一、Stream 理解

在 java 中我们称 Stream 为『』,我们经常会用流去对集合进行一些流水线的操作。stream 就像工厂一样,只需要把集合、命令还有一些参数灌输到流水线中去,就可以加工成得出想要的结果。这样的流水线能大大简洁代码,减少操作。

二、Stream 流程


原集合 —> 流 —> 各种操作(过滤、分组、统计) —> 终端操作
复制代码


Stream 流的操作流程一般都是这样的,先将集合转为流,然后经过各种操作,比如过滤、筛选、分组、计算。最后的终端操作,就是转化成我们想要的数据,这个数据的形式一般还是集合,有时也会按照需求输出 count 计数。下文会一一举例。


1561347807499060294.png

三、API 功能举例

首先,定义一个用户对象,包含姓名、年龄、性别和籍贯四个成员变量:



import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j;
@Data @NoArgsConstructor @AllArgsConstructor @Log4j @Builder public class User { //姓名 private String name; //年龄 private Integer age; //性别 private Integer sex; //所在省市 private String address; }
复制代码


这里用 lombok 简化了实体类的代码。


然后创建需要的集合数据,也就是源数据:



//1.构建我们的list List
复制代码

3.1 过滤

1)创建流 stream() / parallelStream()

  • stream() : 串行流

  • parallelStream(): 并行流

2)filter 过滤(T-> boolean)

比如要过滤年龄在 40 岁以上的用户,就可以这样写:



List filterList = list.stream().filter(user -> user.getAge() >= 40) .collect(toList());
复制代码


filter 里面,->箭头后面跟着的是一个 boolean 值,可以写任何的过滤条件,就相当于 sql 中 where 后面的东西,换句话说,能用 sql 实现的功能这里都可以实现


打印结果:


1561347817719022688.png

3)distinct 去重

和 sql 中的 distinct 关键字很相似。为了看到效果,此处在原集合中加入一个重复的人,就选择钢铁侠吧,复联 4 钢铁侠不幸遇害,大家还是比较伤心的。



List list= Arrays.asList( new User("钢铁侠",40,0,"华盛顿"), new User("钢铁侠",40,0,"华盛顿"), new User("蜘蛛侠",20,0,"华盛顿"), new User("赵丽颖",30,1,"湖北武汉市"), new User("詹姆斯",35,0,"洛杉矶"), new User("李世民",60,0,"山西省太原市"), new User("蔡徐坤”,18,1,"陕西西安市"), new User("葫芦娃的爷爷",70,0,"山西省太原市"));
复制代码



//distinct 去重 List
复制代码


打印结果:


1561347825899075525.png

4)sorted 排序

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如:



Comparator.comparingInt
复制代码


反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口。



//sorted() List
复制代码


打印结果:


1561347833939080167.png


结果按照年龄从小到大进行排序。

5)limit() 返回前 n 个元素

如果想知道这里面年龄最小的是谁,可作如下操作:



//limit 返回前n个元素 List
复制代码


1561347842659011443.png

6)skip()

与 limit 恰恰相反,skip 的意思是跳过,也就是去除前 n 个元素。


打印结果:


1561347850466011976.png


果然,前两个人都被去除了,只剩下最老的葫芦娃爷爷。

3.2 映射

1)map(T->R)

map 是将 T 类型的数据转为 R 类型的数据,比如我们想要设置一个新的 list,存储用户所有的城市信息。



//map(T->R) List
复制代码


打印结果:


1561347859659008716.png

2)flatMap(T -> Stream)

将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流。



//flatMap(T -> Stream)List flatList = new ArrayList<>();flatList.add("唱,跳");flatList.add("rape,篮球,music");flatList = flatList.stream().map(s -> s.split(",")).flatMap(Arrays::stream).collect(toList());
复制代码


打印结果:


1561347865839086113.png


这里原集合中的数据由逗号分割,使用 split 进行拆分后,得到的是 Stream,字符串数组组成的流,要使用 flatMap 的


Arrays::stream


将 Stream 转为 Stream,然后把流相连接,组成了完整的唱、跳、rap、篮球和 music。

3.3 查找

1)allMatch(T->boolean)

检测是否全部满足参数行为,假如这些用户是网吧上网的用户名单,那就需要检查是不是每个人都年满 18 周岁了。



boolean isAdult = list.stream().allMatch(user -> user.getAge() >= 18);
复制代码


打印结果:



true
复制代码

2)anyMatch(T->boolean)

检测是否有任意元素满足给定的条件,比如,想知道同学名单里是否有女生。



//anyMatch(T -> boolean) 是否有任意一个元素满足给定的条件 boolean isGirl = list.stream().anyMatch(user -> user.getSex() == 1);
复制代码


打印结果:



true
复制代码


说明集合中有女生存在。

3)noneMatch(T -> boolean)

流中是否有元素匹配给定的 T -> boolean 条件。


比如检测有没有来自巴黎的用户。



boolean isLSJ = list.stream().noneMatch(user -> user.getAddress().contains("巴黎"));
复制代码


打印结果:



true
复制代码


打印 true 说明没有巴黎的用户。

4)findFirst( ):找到第一个元素


Optional fristUser = list.stream().findFirst();
复制代码


打印结果:



User(name=钢铁侠, age=40, sex=0, address=华盛顿)
复制代码

5)findAny():找到任意一个元素


Optional anyUser = list.stream().findAny();
复制代码


打印结果:



User(name=钢铁侠, age=40, sex=0, address=华盛顿)
复制代码


这里我们发现 findAny 返回的也总是第一个元素,那么为什么还要进行区分呢?因为在并行流 parallelStream() 中找到的确实是任意一个元素。



Optional anyParallelUser = list.parallelStream().findAny();
复制代码


打印结果 :



Optional[User(name=李世民, age=60, sex=0, address=山西省太原市)]
复制代码

3.4 归纳计算

1)求用户的总人数


long count = list.stream().collect(Collectors.counting());
复制代码


我们可以简写为:



long count = list.stream().count();
复制代码


运行结果:



8
复制代码

2)得到某一属性的最大最小值


// 求最大年龄 Optional
复制代码


运行结果:


1561347877340017466.png


1561347884770084319.png

3)求年龄总和是多少


// 求年龄总和 int totalAge = list.stream().collect(Collectors.summingInt(User::getAge));
复制代码


运行结果:



313
复制代码


我们经常会用 BigDecimal 来记录金钱,假设想得到 BigDecimal 的总和:



// 获得列表对象金额, 使用reduce聚合函数,实现累加器 BigDecimal sum = myList.stream() .map(User::getMoney) .reduce(BigDecimal.ZERO,BigDecimal::add);
复制代码

4)求年龄平均值


//求年龄平均值 double avgAge = list.stream().collect( Collectors.averagingInt(User::getAge));
复制代码


运行结果:



39.125
复制代码

5)一次性得到元素的个数、总和、最大值、最小值


IntSummaryStatistics statistics = list.stream().collect( Collectors.summarizingInt(User::getAge));
复制代码


运行结果:


1561347893339037476.png

6)字符串拼接

要将用户的姓名连成一个字符串并用逗号分割。



String names = list.stream().map(User::getName) .collect(Collectors.joining(", "));
复制代码


运行结果:



钢铁侠, 钢铁侠, 蜘蛛侠, 赵丽颖, 詹姆斯, 李世民, 蔡徐坤, 葫芦娃的爷爷
复制代码

3.5 分组

在数据库操作中,我们经常通过 GROUP BY 关键字对查询到的数据进行分组,java8 的流式处理也提供了分组的功能。使用 Collectors.groupingBy 来进行分组。

1)可以根据用户所在城市进行分组


Map<string, list> cityMap = list.stream().collect(Collectors.groupingBy(User::getAddress));</string, list>
复制代码


1561347901940010844.png


结果是一个 map,key 为不重复的城市名,value 为属于该城市的用户列表。已经实现了分组。

2)二级分组,先根据城市分组再根据性别分组


Map<string, map<integer, list>> group = list.stream().collect( Collectors.groupingBy(User::getAddress, // 一级分组,按所在地区 Collectors.groupingBy(User::getSex))); // 二级分组,按性别</string, map<integer, list>
复制代码


运行结果:


1561347910901018179.png

3)如果仅仅想统计各城市的用户个数是多少,并不需要对应的 list

按城市分组并统计人数:



Map cityCountMap = list.stream().collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));
复制代码


运行结果:


1561347919233084466.png

4)当然,也可以先进行过滤再分组并统计人数


Map map = list.stream().filter(user -> user.getAge() <= 30) .collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));
复制代码


运行结果:


1561347926643068727.png

5)partitioningBy 分区

分区与分组的区别在于,分区是按照 truefalse 来分的,因此 partitioningBy 接受的参数的 lambda 也是 T -> boolean



//根据年龄是否小于等于30来分区 Map<boolean, list</boolean, list
复制代码


运行结果:


1561347934675068021.png

总结

到目前为止,stream 的功能我们已经用了很多了,感觉有点眼花缭乱却无所不能,stream 能做的事情远远不止这些。


我们可以多学习使用 stream,把原来复杂的 sql 查询,一遍又一遍地 for 循环的复杂代码重构,让代码更简洁易懂,可读性强。


本文转载自宜信技术学院网站。


原文链接:http://college.creditease.cn/detail/264


2020-02-10 21:071561

评论

发布
暂无评论
发现更多内容

自己动手写Docker系列 -- 5.5实现容器停止

Go Docker 4月月更

10个商品图策略,引导亚马逊卖家提升客户购物体验

龙国富

电商 商品 亚马逊 客户体验

为什么做开源需要懂异步协作?

腾源会

开源 腾源会

直播预告|MobTech携手中国信通院共话数据治理建设实践

MobTech袤博科技

数据安全 火山引擎 信通院 MobTech袤博科技

超nei卷!连黑客勒索软件团伙都开始谈客户体验…

龙国富

黑客 客户体验 drakside 客户至上

java培训关闭流方法有顺序吗

@零度

JAVA开发 包装流

架构实战营作业 - 模块二

haibin369

「架构实战营」

面试官:Redis的事务满足原子性吗?

码农参上

redis 事务 4月月更

净推荐值(NPS)完整行动指南

龙国富

客户体验 NPS 净推荐值 北极星指标 客户忠诚度

华为云GaussDB(for Influx)揭秘第五期:最佳实践之子查询

华为云开发者联盟

数据库 华为云 GaussDB(for Influx) 子查询 InfluxQL

不写代码,带你徒手开发一个健康打卡应用

华为云开发者联盟

无代码 AppCube 应用开发 健康打卡 可视化编排

spring-cloud 链路追踪

Rubble

4月日更

Test

haibin369

十年磨一剑,墨菲安全正式发布开源项目murphysec

墨菲安全

开源安全

快速尝鲜:RabbitMQ 搭建完就得用起来

阿Q说代码

RabbitMQ springboot 4月月更 消息确认机制

脉冲能量|Committer 专访——李理:Apache Pulsar 项目“体验师”

Apache Pulsar

开源 云原生 Apache Pulsar Apache Pulsar 社区 运维‘

状态机引擎在vivo营销自动化中的深度实践 | 引擎篇02

vivo互联网技术

服务器 状态机

微信朋友券的高性能复杂度

CityAnimal

架构实战营 #架构实战营 「架构实战营」

《手写Mybatis》第4章:Mapper XML的解析和注册使用

小傅哥

小傅哥 mybatis 工厂模式 建造者

为啥JS/TS里都会有"use strict"

华为云开发者联盟

js ts 严格模式 use strcit 使用严格

B 端 SaaS 产品自动化事件设计 - 规则表达式

龙国富

自动化 SaaS 正则 B端 规则表达式

13个问题帮你选型 “客户体验管理SaaS平台”

龙国富

SaaS 选型 客户体验 CEM CXM

都2022年了,HDFS为何还如此能战

华为云开发者联盟

大数据 hadoop hdfs 分布式文件系统 大数据存储

如何禁用XXE处理?

龙智—DevSecOps解决方案

漏洞扫描 代码安全 漏洞检测

Excelize 发布 2.6.0 版本,功能强大的 Excel 文档基础库

xuri

golang Excel Apache POI Go 语言 Excelize

使用和风天气 API 10分钟搭建天气预报数据看板

蒋川

API 天气api

企业知识管理的解决方案

小炮

知识管理 企业知识管理 企业知识管理工具

敲重点!这些更新将会影响HarmonyOS原子化服务上架

HarmonyOS开发者

HarmonyOS 原子化服务

一文了解垃圾回收算法中的引用计数算法

宇宙之一粟

垃圾回收算法 引用计数 4月月更

消息队列存储消息数据的MySQL表格设计

Geek_36cc7c

Linux驱动开发-编写PCF8591(ADC)芯片驱动

DS小龙哥

4月月更

简洁方便的集合处理 Java 8 stream流_安全_杨亨_InfoQ精选文章