在软件开发中,一个众所周知的问题就是无论你做什么,用户的需求总会改变。
举个栗子,假设要做一个帮助农场主理解库存的应用。
一开始,农场主可能想有一个在所有库存中找出所有绿色苹果的功能。
但是第二天他可能会告诉你他还想要找到所有重量大于150g的苹果。
两天后,他可能又提出找到所有绿色的并且重量大于150g的苹果。
在面对这些相似的新功能时,我们都想尽可能的减少开发量。
behavior parameterization是用来处理频繁更改的需求的一种软件开发模式,可以将一段代码块当做参数传给另一个方法,之后执行。
流是Java API的新成员,它允许你以声明的方式处理数据集合,简单来说,可以把它当作数据集的高级迭代器。
此外,流还可以透明地并行处理,你无需写任何多线程代码了。
举例说明
举个例子来说明流的好处,有一个简单的场景,要求返回低热量的菜肴名称,并按照卡路里排序,实体代码如下:
基础类
- 菜肴
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
//Getter & Setter
}
在上一篇的读书笔记中,我们已经看到了流让你从外部迭代转向内部迭代。
这样,你就用不着写下面这样的代码来显式地管理数据集合的迭代(外部迭代)了:
/**
* 菜单
*/
public static final List MENU =
Arrays.asList(new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 400, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH));
在本节中,我们会将迄今学到的关于流的知识付诸实践。我们来看一个不同的领域:执行交易的交易员。
你的经理让你为八个查询找到答案。
-
找出2011年发生的所有交易,并按交易额排序(从低到高)。
-
交易员都在哪些不同的城市工作过?
-
查找所有来自于剑桥的交易员,并按姓名排序。
-
返回所有交易员的姓名字符串,按字母顺序排序。
-
有没有交易员是在米兰工作的?
-
打印生活在剑桥的交易员的所有交易额。
-
所有交易中,最高的交易额是多少?
-
找到交易额最小的交易。
我们在前面看到了可以使用 reduce 方法计算流中元素的总和。
例如,你可以像下面这样计算菜单的热量:
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合。
你可以把Java 8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如 filter 或 map )和终端操作(如 count 、 findFirst 、 forEach 和 reduce )。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。
我们已经在前面用过了 collect 终端操作了,当时主要是用来把 Stream 中所有的元素结合成一个 List 。
分区的概念
分区是分组的特殊情况。
- 分区函数
由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。
分区函数返回一个布尔值,这意味着得到的分组 Map 的键类型是 Boolean,于是它最多可以分为两组—— true 是一组, false 是一组。
- 个人理解
因为分区函数的特殊性,将分区单独提取出来进行说明。其优点后面也会说明,在于保留了分区函数返回 true 或 false 的两套流元素列表。
在前面三章中,我们已经看到了新的 Stream 接口可以让你以声明性方式处理数据集。我们还解释了将外部迭代换为内部迭代能够让原生Java库控制流元素的处理。这种方法让Java程序员无需显式实现优化来为数据集的处理加速。到目前为止,最重要的好处是可以对这些集合执行操作流水线,能够自动利用计算机上的多个内核。
例如,在Java 7之前,并行处理数据集合非常麻烦。第一,你得明确地把包含数据的数据结构分成若干子部分。第二,你要给每个子部分分配一个独立的线程。第三,你需要在恰当的时候对它们进行同步来避免不希望出现的竞争条件,等待所有线程完成,最后把这些部分结果合并起来。
Java 7引入了一个叫作分支/合并的框架,让这些操作更稳定、更不易出错。
简介
Spliterator 是Java 8中加入的另一个新接口;这个名字代表“可分迭代器”(splitableiterator)。
和 Iterator 一样,Spliterator 也用于遍历数据源中的元素,但它是为了并行执行而设计的。
虽然在实践中可能用不着自己开发 Spliterator ,但了解一下它的实现方式会让你对并行流的工作原理有更深入的了解。
Java 8已经为集合框架中包含的所有数据结构提供了一个默认的 Spliterator 实现。
接口
集合实现了 Spliterator 接口,接口提供了一个 spliterator 方法。