写点什么

简洁方便的集合处理 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:072026

评论

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

得物 iOS 启动优化之 Building Closure

得物技术

ios dyld

Vision 2025:助力客户取得成功,是英特尔的第一要务

E科讯

看看坐标深圳、薪资15k的go开发岗面试强度如何

王中阳Go

Go 面试 后端

中文集成版 微信多开 WechatTweak for Mac(微信多开、消息防撤回工具)

Rose

字符串替换研究

京东零售技术

【招生简章】司库管理案例实践高端研修班

用友智能财务

财经 会计

Topaz Video AI for mac(地表最强视频无损放大修复工具)v6.1.3激活版

Rose

全能pdf编辑阅读 PDF Reader Pro for mac

Rose

服装智能制造软件平台(源码+文档+讲解+演示)

深圳亥时科技

One4All下一代生成式推荐系统

京东零售技术

One4All下一代生成式推荐系统

京东零售技术

中国上市企业精益战略

管理在线

精益管理体系

Databend Cloud Dashboard 全新升级:直击痛点,释放数据价值

Databend

开源协议不能随便选!选错直接被「背刺」

程序员晚枫

开源

PowerVerse&HiBao Cloud已在杭州、深圳、天津等地部署算力节点

PowerVerse

DePIN 去中心化云算力 DeCloud

食品行业数字化:MES系统解决方案

万界星空科技

mes 食品饮料行业数字化转型 食品MES 食品加工行业

NocoBase 本周更新汇总:优化及缺陷修复

NocoBase

开源 低代码 零代码 无代码 版本更新

ClickHouse 的“独孤九剑”:极速查询的终极秘籍

京东零售技术

星闪破壁,鸿蒙筑家:智慧家庭聚变2025

脑极体

AI

Lightroom Classic 2025 功能介绍及系统要求

Rose

《汽车之家2024年ESG报告》发布 技术创新驱动行业可持续发展

科技大数据

StarRocks 助力首汽约车精细化运营

StarRocks

数据库 spark 存算分离 StarRocks 数据查询

怎么用AI工具一键美化PPT?AI美化PPT步骤详解!

职场工具箱

人工智能 PPT 办公软件 AIGC AI生成PPT

打造探索式测试智能体—Scoutron

技艺的尾巴

AI 智能体 探索式测试

从项目管理到价值管理的转型路径:基于敏捷价值管理办公室(VMO®)的战略升级(上篇)

ShineScrum

PMO cvp VMO

Fabric8 Kubernetes 教程——Metrics、Resource

FunTester

突破极限: 高负载场景下的单机300M多行正则日志采集不是梦

阿里巴巴云原生

阿里云 云原生

终于等到机会!这轮周期牛市预计比特币涨到$50万左右

PowerVerse

课程质量评估系统(源码+文档+讲解+演示)

深圳亥时科技

Pr2024下载,Premiere Pro 2024中文破解版

Rose

什么是 StarRocks?核心优势与适用场景解析

镜舟科技

数据库 存算分离架构 StarRocks 实时分析 极速查询

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