这种处理数据的方式很有用,因为你让Stream API管理如何处理数据。这样StreamAPI就可以在背后进行多种优化。此外,使用内部迭代的话,SteamAPI可以决定并行运行你的代码。这要是用外部迭代的话就办不到了,因为你只能用单一线程挨个迭代。在本章中,你将会看到Stream API支持的许多操作。这些操作能让你快速完成复杂的数据查询,如筛选、切片、映射、查找、匹配和归约。接下来,我们会看看一些特殊的流:数值流、来自文件和数组等多种来源的流,最后是无限流。
5.1 筛选和切片
在本节中,我们来看看如何选择流中的元素:用谓词筛选,筛选出各不相同的元素,忽略流中的头几个元素,或将流截短至指定长度。
5.1.1 用谓词筛选
5.1.3截短流
流支持1imit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给1imit。如果流是有序的,则最多会返回前n个元素。比如,你可以建立一个list,选出热量超过300卡路里的头三道菜:
List<Dish>dishes =men.stream()filter(d->d.getCalories()>300)
limit(3)
collect(toList());
图5-3展示了filter和limit的组合。你可以看到,该方法只选出了符合谓词的头三个元素然后就立即返回了结果。
请注意limit也可以用在无序流上,比如源是一个set。这种情况下,limit的结果不会以任何顺序排列。
5.1.4 跳过元素
流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,1imit(n)和skip(n)是互补的!例如,下面的代码将跳过超过300卡路里的头两道菜,并返回剩下的。图5-4展示了这个查询。
List<Dish>dishes =menu.stream()
filter(d ->d.getCalories()>300)
.skip(2)
.collect(toList())i
在我们讨论映射操作之前,在测验5.1上试试本节学过的内容吧。
测验5.1:筛选
你将如何利用流来筛选前两个荤菜呢?
答案:你可以把filter和limit复合在一起来解决这个问题,并用collect(toList())将流转换成一个列表。
List<Dish>dishes =menu.stream().filter(d->d.getType()== Dish.Type.MEAT).limit(2)
.collect(toList());
5.2映射
一个非常常见的数据处理套路就是从某些对象中选择信息。比如在SQL里,你可以从表中选择一列。StreamAPI也通过map和flatMap方法提供了类似的工具。
5.2.1 对流中每一个元素应用函数
流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建-个新版本”而不是去“修改”)。例如,下面的代码把方法引用Dish::getName传给了map方法来提取流中菜看的名称:
List<String>dishNames =menustream()
.map(Dish::getName )
.collect(toList())i
因为getName方法返回一个string,所以map方法输出的流的类型就是stream<string>让我们看一个稍微不同的例子来巩固一下对map的理解。给定一个单词列表,你想要返回另个列表,显示每个单词中有几个字母。怎么做呢?你需要对列表中的每个元素应用一个函数。这听起来正好该用map方法去做!应用的函数应该接受一个单词,并返回其长度。你可以像下面这样,给map传递一个方法引用string::length来解决这个问题:
List<String>words= Arrays.asList("Java 8","Lambdas","In","Action");List<Integer>wordLengths =words.stream()
.map(String::length)
collect(toList())i
现在让我们回到提取菜名的例子。如果你要找出每道菜的名称有多长,怎么做?你可以像下面这样,再链接上一个map: