引入流

流是什么

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

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

List<String> 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 能够多线程

  • 声明性:更简洁架构

  • 可复合:更灵活函数

  • 可并行:性能更好性能

流简介

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

  • 元素序列:流提供了一个接口,能够访问特定元素类型的一组有序值线程

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

  • 数据处理操做:流的数据处理功能支持相似数据库的声明式操做,能够串行处理也可并行

  • 流水线:不少流操做自己会返回一个流,这样多个操做就能够连接起来,造成一个大的流水线

  • 内部迭代:与使用迭代器显式迭代不一样,流的迭代操做是在背后执行的

为了更直观地理解流以及 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<Dish> 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<String> threeHighCaloriesDishNames = menu
                // 从 menu 得到源
                .stream()
                // Lambda 调用谓词函数式接口过滤卡路里大于400的高热量菜肴
                .filter((dish) -> dish.getCalories() > 400)
                // 将过滤结果映射成对应的菜肴名
                .map(Dish::getName)
                // 按照顺序选择三个
                .limit(3)
                // 保存成 List
                .collect(Collectors.toList());

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

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

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

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

流与集合

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

和迭代器同样,流只能遍历一次,遍历完后咱们就能够说这个流被消费掉了。咱们能够从源再得到一个新的流来从新遍历。

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

Stream<Dish> 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<String> dishNames = new ArrayList<>();
        for (Dish dish : menu) {
            dishNames.add(dish.getName());
        }

流:内部迭代

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

乍一看好像没有多大的区别,可是若是考虑到并行处理呢?若是使用外部迭代则须要咱们很痛苦地本身去处理并行,而用流则很是简单,只须要把 stream 换成 parallelStream。

流的使用通常包括三件事:

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

  • 一个中间操做链,造成一条流水线

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

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

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

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

相关文章
相关标签/搜索