流是什么
流是JavaAPI的新成员,以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现),流还可以并行处理,无需写任何多线程的代码。
从菜单列表中筛选热量少于400的菜,然后对这些菜按照热量进行升序排列,最后获得这些菜单的名称列表。
没有使用流,普通处理:
//筛选热量小于400的菜
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish : menus){
if (dish.getCalories() < 400){
lowCaloricDishes.add(dish);
}
}
//热量小于400的菜,根据热量进行排序
Collections.sort(lowCaloricDishes, new Comparator<Dish>(){
@Override
public int compare(Dish o1, Dish o2) {
return Integer.compare(o1.getCalories(), o2.getCalories());
}
});
//热量小于400的菜,获得菜名单列表
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish dish : lowCaloricDishes){
lowCaloricDishesName.add(dish.getName());
}
使用流进行处理:
List<String> lowCaloricDishesName2 = menus.stream()
.filter(d -> d.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
使用流多线程处理:
List<String> lowCaloricDishesName2 = menus.parallelStream()
.filter(d -> d.getCalories() < 400)
.sorted(Comparator.comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
- 代码是以声明性方式写的:说明想要完成什么(筛选热量低于400的菜),而不是说明如何实现操作。
- 可以把几个操作链接起来,表达复杂的数据处理流水线
使用StreamAPI可以写出这样的代码:
- 声明性:更简洁,更易读
- 可符合:更灵活
- 可并行:性能更好
流简介
流到底是什么?简短的定义就是“从支持数据处理操作的源生成元素序列”
- 元素序列:就像集合一样,流也提供一个接口,可以访问特定元素类型的一组有序值。集合是数据结构,主要目的是以特定的事件/空间复杂度存储和访问元素(如ArrayList、LinkedList),但流的目的在于表达计算(filter、sorted、map)。集合关注的是数据,流关注的是计算。
- 源:流使用一个提供数据的源,比如集合、数组或者I/O资源,从有序集合生成时会保留原有的顺序,由列表生成的流,其元素顺序于列表一致。
- 数据处理操作:流的数据处理功能支持类次于数据库的操作,以及函数式编程语言中的常用操作,比如:filter、map、reduce、find、match、sort等。
- 流水线:很多流操作本身会返回一个流,多个操作就可以链接起来,构成一个更大的流水线。
- 内部迭代:集合使用迭代器进行显示迭代,流的迭代操作是在后台进行。
- filter:接受一个Lambda,从流中排除某些元素。在本例中选择热量大于300的卡路里。
- map:接受一个Lambda,将元素转换成其他形式或提取信息。在本例中提取菜单的名称。
- limit:截断流,使其元素不超过给定数量。
- collect:将流转换成其他形式。
流与集合
流与集合的差异在于什么时候进行计算。
集合是一个内存中的数据结构,它包含数据结构中目前所有的值,集合中的每个元素都得先计算出来才能添加到集合中。
流则是在概念上固定的数据结构(不能添加和删除元素),其元素是按需计算,用户从流中提取需要的值,而这些值----在用户看不见得地方,只会按需生成,这是一种生产者-消费者得关系,从另外一个角度来说,流就像是一个延迟创建得集合,只有在消费者要求得时候才会计算值。
只能遍历一次
流只能遍历一次,遍历完后,我们就说这个流已经被消费掉了。
外包迭代与内部迭代
使用Collection接口需要用户去做迭代(比如for-each),这称为外部迭代,相反,Stream库使用内部迭代-----他帮你把迭代做了,还把得到得流值存在了某个地方,你只要给出一个函数说要干什么就可以。
外部迭代:
内部迭代:
流操作
流操作有两大类,可以连接起来的操作称为中间操作,关闭流的操作称为终端操作。
1、中间操作
filter或sorted等中间操作会返回另一个流,让多个操作可以连接起来形成一个查询。除非流水线上触发一个终端操作,否则中间操作不会执行任何处理---他很懒。因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
filter | 中间 | Stream<T> | Predicate<T> | T -> boolean |
map | 中间 | Stream<T> | Function<T,R> | T -> R |
limit | 中间 | Stream<T> | ||
sorted | 中间 | Stream<T> | Comparator<T> | (T,T) -> int |
distinct | 中间 | Stream<T> |
2、终端操作
终端操作会从流的流水线生成结果,其结果不时流,而是List、Integer、甚至void。
操作 | 类型 | 返回类型 | 目的 |
forEach | 终端 | void | 消费流中的每个元素并对其应用Lambda |
count | 终端 | long | 返回流中元素的个数 |
collect | 终端 | generic | 把流归约成一个集合,比如List、Map,甚至是Integer |
使用流
使用流一般包括三件事:
- 一个数据源来执行一个查询
- 一个中间操作,形成一条流的流水线
- 一个终端操作,执行流水线,并能生成结果