Java开发笔记(七十二)Java8新增的流式处理
通过前面几篇文章的学习,大家应能掌握几种容器类型的常见用法,对于简单的增删改和遍历操作,各容器实例都提供了相应的处理方法,对于实际开发中频繁使用的清单List,还能利用Arrays工具的asList方法给清单对象做初始化赋值,另外提供了专门的Collections工具进行排序、求最大元素、求最小元素等操作。那么涉及到更加复杂的数据处理,游荡如何有针对性地筛选和进一步加功能?
依次遍历目标容器,对所有元素逐个加以分析判断,并酌情将具体数据调整至满意的状态,这种千篇一律的业务流程固然能够解决问题,可惜由此带来的副作用是显而易见的,包括但不限于:代码冗长、分支众多、逻辑繁琐、不易重用等等。为了改进相关业务逻辑的编程方式,帮助开发者形成良好的编码风格,Java的每次版本更新都试图给出有效的解决方案,其中影响深远的当数Java8推出的两项新特性:新增的泛型接口与流式处理。关于前一个泛型接口特性,用于容器操作的泛型接口主要有三个,分别是断言接口、消费接口和函数接口,有关的应用案例可参见之前的泛型接口文章,这里不再赘述。真正具有革命性意义的才是本文的主角——流式处理。
所谓流,隐含着流水线的意思,也就是由开发者事先设定一批处理指令,说明清楚每条指令的前因后果,然后启动流水线作业,即可得到最终的处理结果。流式处理的精髓在于一气呵成,只要万事俱备,决不拖泥带水。开展流式处理主要包括三个步骤:获得容器的流对象、设置流的各项筛选和加工指令,以及规划处理结果的展示形式。下面就分别予以详细介绍。
1、获得容器的流对象
Java8给每种容器都准备了两条流水线,一条是串行流,另一条是并行流。串行流顾名思义各项任务是前后串在一起的,只有处理完前一项任务,才能继续执行后一项任务。调用容器实例的stream方法即可获得该容器的串行流对象,而调用容器实例的parallelStream方法可获得该容器的并行流对象。
流对象的获取操作同时也是流式处理的开始指令,每次进行流式处理之前,都必须先获取当前容器的流对象,要么获取串行流,要么获取并行流。
2、设置流的各项筛选和加工指令
不管是串行流还是并行流,它们承载的都是容器内部的原始数据,这些原材料要经过各道加工工序,之后才会得到具备初步形态的半成品。加工数据期间所调用的流方法说明如下:
filter:按照指定条件过滤。即筛选出符合条件的那部分数据。
sorted:根据指定字段对所有记录排序。可选择升序或者降序。
map:映射成指定的数据类型。
limit:只取前面若干条数据。
distinct:去掉重复记录。保证每条记录都是唯一的。
以上的加工方法属于流式处理的中间指令,每次流水线作业都允许设置一条或者多条中间指令。
3、规划处理结果的展示形式
前一步的各项加工处理完毕,还要弄个包装才能输出最终的成品,也就是这条流水线生产出来的数据到底长什么模样。结果数据的记录包装有三种形式,分别对应如下的三个方法:
count:统计结果数据的数量。
forEach:依次遍历结果数据,并逐条进行个性化处理。
collect:搜集和整理结果数据,并返回指定格式的清单记录。
上面的三个包装方法属于流式处理的结束指令,每次流水线作业必须配备有且仅有其中的一条结束指令。
接下来列举几个实际应用的业务场景,看看采取流式处理时该如何编码。首先准备一个原始的苹果清单,后续将对这个苹果清单发动流水作业。原始清单的获取代码示例如下:
- // 获取默认的苹果清单
- private static ArrayList<Apple> getAppleList() {
- ArrayList<Apple> appleList = new ArrayList<Apple>();
- appleList.add(new Apple("红苹果", "RED", 150d, 10d));
- appleList.add(new Apple("大苹果", "green", 250d, 10d));
- appleList.add(new Apple("红苹果", "red", 300d, 10d));
- appleList.add(new Apple("大苹果", "yellow", 200d, 10d));
- appleList.add(new Apple("红苹果", "green", 100d, 10d));
- appleList.add(new Apple("大苹果", "Red", 250d, 10d));
- return appleList;
- }
然后需要统计红苹果总数的话,可通过下列的流式代码开展统计操作:
- // 统计红苹果的总数
- long redCount = getAppleList().stream() // 串行处理
- .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果
- .count(); // 统计记录个数
- System.out.println("红苹果总数=" + redCount);
注意到上述代码的filter方法内部出现了方法引用,的确流式处理的主要方法都预留了函数式接口的调用,所以经常会在流式代码中看到五花八门的方法引用与Lambda表达式。比如下面的结果遍历代码就在forEach方法中填充了Lambda表达式:
- // 对每个红苹果依次进行处理
- getAppleList().stream() // 串行处理
- .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果
- .forEach(s -> System.out.println("当前颜色为"+s.getColor())); // 逐条开展操作
当然流水作业更常见的输出另一串清单数据,此时流式处理的结束指令就得采用collect方法。下面便是从原始清单中挑出红苹果清单的流式代码:
- // 挑出红苹果清单
- List<Apple> redAppleList = getAppleList().stream() // 串行处理
- //.parallelStream() // 并行处理
- .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果
- .sorted(Comparator.comparing(Apple::getWeight)) // 按苹果重量升序排列
- //.sorted(Comparator.comparing(Apple::getWeight).reversed()) // 按苹果重量降序排列
- .limit(3) // 只取前几条数据
- .distinct() // 去掉重复记录
- .collect(Collectors.toList()); // 返回一串清单
- System.out.println("红苹果清单=" + redAppleList.toString());
结果清单可能不需要完整的苹果信息,只需列出苹果名称字段,那么得调用map方法把完整的苹果信息映射为单个的名称字段。此时的筛选代码变成下面这样:
- // 挑出去重后的苹果名称清单
- List<String> allNameList = getAppleList().stream() // 串行处理
- .map(Apple::getName) // 映射成新的数据类型
- .distinct() // 去掉重复记录
- .collect(Collectors.toList()); // 返回一串清单
- System.out.println("苹果名称去重后的清单=" + allNameList.toString());
除了普通的清单,collect方法还能返回分组清单,也就是把结果数据按照某种条件进行分组,再统计每个分组的成员数目。仍以苹果清单为例,红苹果可通过名称或者产地分组,分组的同时计算每个小组里各有多少粒苹果。于是形成了以下的分组计数代码:
- // 按照名称统计红苹果的分组个数
- Map<String, Long> redStatisticCount = getAppleList().stream() // 串行处理
- .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果
- .collect(Collectors.groupingBy(Apple::getName, Collectors.counting())); // 返回分组计数
- System.out.println("红苹果分组计数=" + redStatisticCount.toString());
分组计数仅仅是简单统计各组的成员数量,有时还想单独计算某个字段的统计值,比如每个小组里的苹果总价各是多少?这时collect方法必须同时完成两项任务,第一项要根据某种条件分组,第二项要对各组的苹果价格求和,如此改造之后的分组求和代码如下所示:
- // 按照名称统计红苹果的分组总价
- Map<String, Double> redPriceSum = getAppleList().stream() // 串行处理
- .filter(Apple::isRedApple) // 过滤条件。专门挑选红苹果
- .collect(Collectors.groupingBy(Apple::getName, Collectors.summingDouble(Apple::getPrice))); // 返回分组并对某字段求和
- System.out.println("红苹果分组总价=" + redPriceSum.toString());
观察以上的具体案例,发现流式处理的代码相当连贯,每个步骤该做什么事情都一清二楚,中间没有许多繁复的流程控制,唯有一条条分工明确的处理指令,同时充分发挥了方法引用及Lambda表达式的便利性,使得原本令人头痛的容器加工变成了有章可循的流水线作业,从而极大地提高了开发者的编码效率。
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(七十二)Java8新增的流式处理的更多相关文章
- Java开发笔记(十二)布尔变量论道与或非
在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...
- Java开发笔记(七十)Java8新增的几种泛型接口
由于泛型存在某种不确定的类型,因此很少直接运用于拿来即用的泛型类,它更经常以泛型接口的面目出现.例如几种基本的容器类型Set.Map.List都被定义为接口interface,像HashSet.Tre ...
- Java开发笔记(一百二十)AWT文本标签
前面介绍了AWT窗口及其面板的简单用法,其中展示出来的控件只有按钮一种,还有很多好用好玩的控件有待介绍.首先是文本标签Label,该控件用于显示一段平铺文本,它不花哨也不跳动,完全就是素面朝天的文本字 ...
- Java开发笔记(一百二十五)AWT图像加工
前面介绍了如何使用画笔工具Graphics绘制各种图案,然而Graphics并不完美,它的遗憾之处包括但不限于:1.不能设置背景颜色:2.虽然提供了平移功能,却未提供旋转功能与缩放功能:3.只能在控件 ...
- Java开发笔记(一百二十六)Swing的窗口
前面介绍了AWT界面编程的若干技术,在编码实践的时候,会发现AWT用起来甚是别扭,它的毛病包括但不限于下列几点:1.对中文的支持不好,要想在界面上正常显示汉字,还得在运行时指定额外的运行参数“-Dfi ...
- Java开发笔记(一百二十二)AWT选择框
前面介绍了两种文本输入框的用法,不过实际应用很少需要用户亲自文字,而是在界面上列出几个选项,让用户勾勾点点完成选择,这样既方便也不容易弄错.依据选择的唯一性,可将选项控件分为两类:一类是在方框中打勾的 ...
- Java开发笔记(一百二十四)AWT绘图操作
前面介绍了如何使用画笔在控件上展示图像,可是图像来源于磁盘图片,无法即兴绘制个性化的图案.所幸画笔工具Graphics不仅能够描绘图像,还支持绘制常见的几何形状,也支持绘制文本字符串,除了绘制图像用到 ...
- Java开发笔记(一百二十八)Swing的图标
前面提过,AWT没提供能够直接显示图像的控件,这无疑是个令人诟病的短板,因为一上来就得由程序员自己去定义新控件,对于初学者来讲很不友好.这个问题在Swing中也解决掉了,不过Swing并未提供单独的图 ...
- Java开发笔记(一百二十九)Swing的输入框
Swing的输入框仍然分成两类:单行输入框和多行输入框,但与AWT的同类控件相比,它们在若干细节上有所调整.首先说单行输入框,AWT的单行输入框名叫TextField,平时输入什么字符它便显示什么字符 ...
随机推荐
- 菜鸡谈OO 第二单元总结
“欢迎来到(玄学)多线程的新世界” Homework1 单部傻瓜电梯调度 Part1 多线程设计策略 第一次学到了线程这个概念,与之前的编程体验大有不同.最大的区别在于从原本的线性发生程序变成了多个行 ...
- selenium python 设置窗口打开大小
1. 窗口最大化 1 driver.maximize_window() 2. 设置窗口大小 1 driver.set_window_size(1920,1080) #分辨率1920 x 1080
- 封装一个 员工类 使用preparedStatement 查询数据 (2) 使用 arrayList 集合
创建 员工=类生成 有参构造 get set 方法 toString 方法 package cn.hph; public class emp1 { //创建员工类的属性 private int id; ...
- Winsock编程基础2(Winsock编程流程)
1.套接字的创建和关闭 //创建套接字 SOCKET socket( int af, //指定套接字使用的地址格式,Winsock只支持AF_INET int type, //套接字类型 int pr ...
- php.ini中文翻译版--转载
;;;;;;;; ; 警告 ; ;;;;;;;;;;; ; 此配置文件是对于新安装的PHP的默认设置. ; 默认情况下,PHP使用此配置文件安装 ; 此配置针对开发目的,并且*不是*针对生产环境 ; ...
- Linux安装Gradle
Linux安装Gradle Gradle 是以 Groovy 语言为基础,面向Java应用为主.基于DSL(领域特定语言)语法的自动化构建工具.在github上,gradle项目很多,有的是gra ...
- 字符串匹配(一)----Rabin-Karp算法
题目:假如要判断字符串A"ABA"是不是字符串B"ABABABA"的子串. 解法一:暴力破解法, 直接枚举所有的长度为3的子串,然后依次与A比较,这样就能得出匹 ...
- python高级-生成器(17)
1. 什么是⽣成器 通过列表⽣成式,我们可以直接创建⼀个列表.但是,受到内存限制,列表容量肯定是有限的.⽽且,创建⼀个包含100万个元素的列表,不仅占⽤很⼤的存储空间,如果我们仅仅需要访问前⾯⼏个元素 ...
- Python—day17时间模块、系统模块、递推遍历、序列化
一.time'''时间戳(timestamp):time.time()延迟线程的运行:time.sleep(secs)(指定时间戳下的)当前时区时间:time.localtime([secs])(指定 ...
- Python内置函数(32)——input
英文文档: input([prompt]) If the prompt argument is present, it is written to standard output without a ...