四时宝库

程序员的知识宝库

第4章 引入流(引入流水步距概念的目的)

流是什么

流是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等。
  • 流水线:很多流操作本身会返回一个流,多个操作就可以链接起来,构成一个更大的流水线。
  • 内部迭代:集合使用迭代器进行显示迭代,流的迭代操作是在后台进行。


  1. filter:接受一个Lambda,从流中排除某些元素。在本例中选择热量大于300的卡路里。
  2. map:接受一个Lambda,将元素转换成其他形式或提取信息。在本例中提取菜单的名称。
  3. limit:截断流,使其元素不超过给定数量。
  4. 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

使用流

使用流一般包括三件事:

  • 一个数据源来执行一个查询
  • 一个中间操作,形成一条流的流水线
  • 一个终端操作,执行流水线,并能生成结果

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接