通过前面几篇文章的学习,大家应能掌握几种容器类型的常见用法,对于简单的增删改和遍历操作,各容器实例都提供了相应的处理方法,对于实际开发中频繁使用的清单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新增的流式处理的更多相关文章

  1. Java开发笔记(十二)布尔变量论道与或非

    在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...

  2. Java开发笔记(七十)Java8新增的几种泛型接口

    由于泛型存在某种不确定的类型,因此很少直接运用于拿来即用的泛型类,它更经常以泛型接口的面目出现.例如几种基本的容器类型Set.Map.List都被定义为接口interface,像HashSet.Tre ...

  3. Java开发笔记(一百二十)AWT文本标签

    前面介绍了AWT窗口及其面板的简单用法,其中展示出来的控件只有按钮一种,还有很多好用好玩的控件有待介绍.首先是文本标签Label,该控件用于显示一段平铺文本,它不花哨也不跳动,完全就是素面朝天的文本字 ...

  4. Java开发笔记(一百二十五)AWT图像加工

    前面介绍了如何使用画笔工具Graphics绘制各种图案,然而Graphics并不完美,它的遗憾之处包括但不限于:1.不能设置背景颜色:2.虽然提供了平移功能,却未提供旋转功能与缩放功能:3.只能在控件 ...

  5. Java开发笔记(一百二十六)Swing的窗口

    前面介绍了AWT界面编程的若干技术,在编码实践的时候,会发现AWT用起来甚是别扭,它的毛病包括但不限于下列几点:1.对中文的支持不好,要想在界面上正常显示汉字,还得在运行时指定额外的运行参数“-Dfi ...

  6. Java开发笔记(一百二十二)AWT选择框

    前面介绍了两种文本输入框的用法,不过实际应用很少需要用户亲自文字,而是在界面上列出几个选项,让用户勾勾点点完成选择,这样既方便也不容易弄错.依据选择的唯一性,可将选项控件分为两类:一类是在方框中打勾的 ...

  7. Java开发笔记(一百二十四)AWT绘图操作

    前面介绍了如何使用画笔在控件上展示图像,可是图像来源于磁盘图片,无法即兴绘制个性化的图案.所幸画笔工具Graphics不仅能够描绘图像,还支持绘制常见的几何形状,也支持绘制文本字符串,除了绘制图像用到 ...

  8. Java开发笔记(一百二十八)Swing的图标

    前面提过,AWT没提供能够直接显示图像的控件,这无疑是个令人诟病的短板,因为一上来就得由程序员自己去定义新控件,对于初学者来讲很不友好.这个问题在Swing中也解决掉了,不过Swing并未提供单独的图 ...

  9. Java开发笔记(一百二十九)Swing的输入框

    Swing的输入框仍然分成两类:单行输入框和多行输入框,但与AWT的同类控件相比,它们在若干细节上有所调整.首先说单行输入框,AWT的单行输入框名叫TextField,平时输入什么字符它便显示什么字符 ...

随机推荐

  1. js中的cookie

    cookie就是一个存放数据的东西,存储量很小4kb,存放在客户端上和应用设备上. 应用场景 用户注册,用户登录,购物车. Chrome浏览器在计算机中存放cookie的位置 C:\Users\Adm ...

  2. 【从零开始搭建自己的.NET Core Api框架】(三)集成轻量级ORM——SqlSugar:3.1 搭建环境

    系列目录 一.  创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...

  3. [Swift]LeetCode782. 变为棋盘 | Transform to Chessboard

    An N x N board contains only 0s and 1s. In each move, you can swap any 2 rows with each other, or an ...

  4. [Swift]LeetCode839. 相似字符串组 | Similar String Groups

    Two strings X and Y are similar if we can swap two letters (in different positions) of X, so that it ...

  5. [Swift]LeetCode911. 在线选举 | Online Election

    In an election, the i-th vote was cast for persons[i] at time times[i]. Now, we would like to implem ...

  6. Hibernate框架笔记04HQL_QBC查询详解_抓取策略优化机制

    目录 1. Hibernate的查询方式 1.1 方式一:OID查询 1.2 方式二:对象导航查询 1.3 方式三:HQL方式 1.4 方式四:QBC查询 1.5 方式五:SQL查询 2. 环境搭建 ...

  7. Java引入的一些新特性

    Java引入的一些新特性 Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本. Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程, ...

  8. Xapian使用入门

    关键字:搜索引擎.Xapian 一篇拖了两三年的入门总结文章,今天发出来,一方面是自己的总结,另一方面是给自己和他人的备忘.读者需要对搜索引擎有初步了解,譬如了解倒排.term.doc.相似度打分等概 ...

  9. 从源码分析如何优雅的使用 Kafka 生产者

    前言 在上文 设计一个百万级的消息推送系统 中提到消息流转采用的是 Kafka 作为中间件. 其中有朋友咨询在大量消息的情况下 Kakfa 是如何保证消息的高效及一致性呢? 正好以这个问题结合 Kak ...

  10. 信息摘要算法之三:SHA256算法分析与实现

    前面一篇中我们分析了SHA的原理,并且以SHA1为例实现了相关的算法,在这一片中我们将进一步分析SHA2并实现之. 1.SHA简述 前面的篇章中我们已经说明过,SHA实际包括有一系列算法,分别是SHA ...