4.1 流是什么
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
4.2 流简介
流:从支持数据处理操作的源生成的元素序列
List<String> list = menu.stream().filter(e -> e.getCalories() < 400 && e.isVegetarian()).map(Dish::getName).limit(3L).collect(toList());
menu是一个List集合数据;
通过stream()转换成流;
filter,map,limit都是对流的一系列数据处理,称为中间操作;
最后的collect(toList())把流转换成List,称为终端操作;
在调用collect之前,没有任何结果产生,实际上根本就没有从menu里选择元素。你可以这么理解:链中的方法调用都在排队等待,直到调用collect。
4.3 流与集合
集合与流之间的差异就在于什么时候进行计算。
集合可以删除或添加元素;而流则是固定的数据结构,其元素是按需计算的
从另一个角度来说,流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值(用管理学的话说这就是需求驱动,甚至是实时制造)。
比如说存在DVD里的电影,这就是一个集合(也许是字节,也许是帧,这个无所谓),因为它包含了整个数据结构。现在再来想想在互联网上通过视频流看同样的电影。现在这是一个流(字节流或帧流)。流媒体视频播放器只要提前下载用户观看位置的那几帧就可以了,这样不用等到流中大部分值计算出来,你就可以显示流的开始部分了(想想观看直播足球赛)。特别要注意,视频播放器可能没有将整个流作为集合,保存所需要的内存缓冲区——而且要是非得等到最后一帧出现才能开始看,那等待的时间就太长了。出于实现的考虑,你也可以让视频播放器把流的一部分缓存在集合里,但和概念上的差异不是一回事。
4.3.1 只能遍历一次
流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍。
4.3.2 遍历数据方式的区别
Collection接口的迭代属于外部迭代;
Stream库使用内部迭代.
//集合:用外部的迭代器做外部迭代
List<String> names = new ArrayList<>();
for(Dish d: menu){ ←─显式顺序迭代菜单列表
names.add(d.getName()); ←─提取名称并将其添加到累加器
}
//流:内部迭代
List<String> names = menu.stream()
.map(Dish::getName) ←─用getName 方法参数化map,提取菜名
.collect(toList()); ←─开始执行操作流水线;没有迭代!
4.4 流操作
4.4.1 中间操作
List<String> names =
menu.stream()
.filter(d -> {
System.out.println("filtering" + d.getName());
return d.getCalories() > 300;
}) ←─打印当前筛选的菜肴
.map(d -> {
System.out.println("mapping" + d.getName());
return d.getName();
}) ←─提取菜名时打印出来
.limit(3)
.collect(toList());
System.out.println(names);
//此代码执行时将打印:
filtering pork
mapping pork
filtering beef
mapping beef
filtering chicken
mapping chicken
[pork, beef, chicken]
你会发现,有好几种优化利用了流的延迟性质。第一,尽管很多菜的热量都高于300卡路里,但只选出了前三个!这是因为limit操作和一种称为短路的技巧。第二,尽管filter和map是两个独立的操作,但它们合并到同一次遍历中了(我们把这种技术叫作循环合并)。
4.4.2 终端操作
终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、Integer,甚至void。
4.4.3 使用流
流的使用一般包括三件事:
一个数据源(如集合)来执行一个查询;
一个中间操作链,形成一条流的流水线;
一个终端操作,执行流水线,并能生成结果。
4.5 小结
流是“从支持数据处理操作的源生成的一系列元素”。
流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了。
流操作有两类:中间操作和终端操作。
filter和map等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流水线,但并不会生成任何结果。
forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
流中的元素是按需计算的。