资讯专栏INFORMATION COLUMN

引入流

phodal / 1204人阅读

摘要:流的使用一般包括三件事一个数据源来执行一个查询一个中间操作链,形成一条流水线一个终端操作,执行流水线并生成结果以上便是流的一些基础知识,下一章会更加深入理解流。实战第四章引入流读书笔记欢迎加入咖啡馆的春天。

流是什么

几乎每个 Java 应用都会制造和处理集合。 允许我们以声明性方式处理集合(通过类似于 SQL 查询语句来表达,而不是临时写一个实现)。此外 还可以透明地并行处理,无需写任何多线程代码。

先看一个例子,混混眼熟就行

        List result = dishList
                .parallelStream()
                // 过滤
                .filter(d -> d.getCalories() < 400)
                // 排序
                .sorted(Comparator.comparing(Dish::getCalories))
                // 映射
                .map(Dish::getName)
                // 保存
                .collect(Collectors.toList());

而且以 parallelStream 调用会利用多核架构并行执行这段代码,如果想要单线程处理,只需要把 parallelStream 换成 stream。

从代码可以看出,它是以声明性方式写的:filter、sorted、map 和 collect。如同 SQL 中的 SELECT、FROM、WHERE 一样。

Java 8中的 Stream API 可以

声明性:更简洁

可复合:更灵活

可并行:性能更好

流简介

流的定义是 从支持数据处理操作的源生成的元素序列

元素序列:流提供了一个接口,可以访问特定元素类型的一组有序值

源:流会使用一个提供数据的源,如集合、数组或输出/输出

数据处理操作:流的数据处理功能支持类似数据库的声明式操作,可以串行处理也可并行

流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线

内部迭代:与使用迭代器显式迭代不同,流的迭代操作是在背后执行的

为了更直观地理解流以及 Lambda 在其的应用,我们可以先创建一个菜肴类

public class Dish {
    // 名字
    private String name;
    // 是否素食
    private Boolean vegetarian;
    // 卡路里
    private Integer calories;
    // 类型
    private Type type;

    public Dish(String name, Boolean vegetarian, Integer calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    // 类型:肉、鱼、其他
    public enum Type {
        MEAT, FISH, OTHER
    }

    // Getter and Setter
}

然后用构造函数定义一个菜单 List,好让我们对其进行流的演示

        List menu = new ArrayList<>();
        // 猪肉:非素食,800卡路里,肉类
        menu.add(new Dish("pork", false, 800, Dish.Type.MEAT));
        // 牛肉:非素食,700卡路里,肉类
        menu.add(new Dish("beef", false, 700, Dish.Type.MEAT));
        // 米饭:素食,359卡路里,其他
        menu.add(new Dish("rice", true, 350, Dish.Type.OTHER));
        // 披萨:素食,550卡路里,其他
        menu.add(new Dish("pizza", true, 550, Dish.Type.OTHER));
        // 对虾:非素食,350卡路里,鱼类
        menu.add(new Dish("prawns", false, 350, Dish.Type.FISH));
        // 三文鱼:非素食,450卡路里,鱼类
        menu.add(new Dish("salmon", false, 450, Dish.Type.FISH));

接下来我们使用流找出头三个高热量菜肴的名字

        List threeHighCaloriesDishNames = menu
                // 从 menu 获得源
                .stream()
                // Lambda 调用谓词函数式接口过滤卡路里大于400的高热量菜肴
                .filter((dish) -> dish.getCalories() > 400)
                // 将过滤结果映射成对应的菜肴名
                .map(Dish::getName)
                // 按照顺序选择三个
                .limit(3)
                // 保存成 List
                .collect(Collectors.toList());

其中两个 Lambda 对应的是以下的快捷写法

        // 返回一个判断卡路里是否高于400的谓词接口对象
        Predicate dishPredicate = (Dish dish) -> dish.getCalories() > 400;
        // 返回一个映射菜肴名称的映射接口对象
        Function dishStringFunction = (Dish dish) -> dish.getName();

如果看不懂请回顾上两章内容。涉及到行为参数化和 Lambda 的多种可简写方式。

在这个小小的例子里,有很多概念。我们先是对 menu 调用 stream 方法得到一个流,所以可以称 menu 为 。它给流提供一个 元素序列,接下来对流进行的一系列 数据处理操作 都会返回另一个流:filter、 map、limit,这样它们就可以拼接成一条 流水线。最后 collect 操作开始处理流水线,并返回结果。在调用 collect 之前没有任何结果产生,实际上根本就没有从 menu 里选择元素。可以这么说,在流水线里的方法调用都在排队等待,直到调用 collect。

流与集合

粗略地说,流与集合之间的差异就在于什么时候进行计算。流是在概念上固定的数据结构,其元素则是按需计算的。例如,尽管质数有无穷多个,但我们仅仅需要从流中提取需要的值,而这些值只会按需生成。与此相反,集合想要创建一个包含所有质数的集合,那会计算起来没完没了,因为总有新的质数要计算,这样我们就不能从中取得需要的值。

和迭代器一样,流只能遍历一次,遍历完后我们就可以说这个流被消费掉了。我们可以从源再获得一个新的流来重新遍历。

用同一个流遍历 menu 两次会抛出异常

        Stream dishStream = menu.stream();
        // 等同于 dishStream.forEach((Dish dish) -> System.out.println(dish));
        dishStream.forEach(System.out::println);
        // java.lang.IllegalStateException: stream has already been operated upon or closed
        // 流已经被操作或关闭
        dishStream.forEach(System.out::println);
外部迭代与内部迭代

集合需要我们手动去做迭代,比如 for-each,这样被称为外部迭代。相反,流使用内部迭代。

集合:使用 for-each 循环外部迭代

        List dishNames = new ArrayList<>();
        for (Dish dish : menu) {
            dishNames.add(dish.getName());
        }

流:内部迭代

        List dishNames = menu
                .stream()
                .map(Dish::getName)
                .collect(Collectors.toList());

乍一看好像没有多大的区别,但是如果考虑到并行处理呢?如果使用外部迭代则需要我们很痛苦地自己去处理并行,而用流则非常简单,只需要把 stream 换成 parallelStream。

流的使用一般包括三件事:

一个数据源来执行一个查询

一个中间操作链,形成一条流水线

一个终端操作,执行流水线并生成结果

以上便是流的一些基础知识,下一章会更加深入理解流。

Java 8 实战 第四章 引入流 读书笔记

欢迎加入咖啡馆的春天(338147322)。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70217.html

相关文章

  • 《java 8 实战》读书笔记 -第四章 引入

    摘要:第四章引入流一什么是流流是的新成员,它允许你以声明性方式处理数据集合通过查询语句来表达,而不是临时编写一个实现。 第四章 引入流 一、什么是流 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码。 下面两段代码都是用来返回低...

    jeyhan 评论0 收藏0
  • 《Java8实战》-第四章读书笔记(引入Stream)

    摘要:内部迭代与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。流只能遍历一次请注意,和迭代器类似,流只能遍历一次。 流(Stream) 流是什么 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!我会在后面的笔记中...

    _ivan 评论0 收藏0
  • nodeJs文件系统(fs)与(stream)

    摘要:异步文件系统不会阻塞程序的执行,而是在操作完成时,通过回调函数将结果返回。 文件系统(File System): 在Node中,文件系统的交互是非常重要的,服务器的本质就是将本地的文件发送给客户端, Node通过fs模块来和文件系统进行交互,该模块提供了一些标准的文件访问API类打开、读取、写入文件、以及与其交互。 要是用fs模块,首先要从核心模块中加载; 使用 const fs= ...

    0x584a 评论0 收藏0
  • Java8新特性总览

    摘要:新特性总览标签本文主要介绍的新特性,包括表达式方法引用流默认方法组合式异步编程新的时间,等等各个方面。还有对应的和类型的函数连接字符串广义的归约汇总起始值,映射方法,二元结合二元结合。使用并行流时要注意避免共享可变状态。 Java8新特性总览 标签: java [TOC] 本文主要介绍 Java 8 的新特性,包括 Lambda 表达式、方法引用、流(Stream API)、默认方...

    mayaohua 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<