JDK8之Stream流

基于<Java+8实战>这本书的读书笔记.不是很全.
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不
是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
先来个栗子感受一下:
在这里插入图片描述
在这里插入图片描述
(先不必纠结这段代码究竟什么意思,看完之后就能理解了。)


1.引入流

1.1 什么是流?

到底什么是流?简短的定义就是:从支持数据处理操作的源生成的元素序列
剖析上面的定义:

  • 元素序列— 就像集合一样,流也提供一个接口,可以访问特定元素类型的一组有序值。集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList),但流的目的在于表达计算。集合讲的数据,流讲的是计算。
  • — 流会使用一个提供数据的源,如集合,数组或输入输出资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。
  • 数据处理操作— 流的数据处理功能支持类似于数据库的操作,以及函数编程语言中的常用操作,如:filter,map,reduce,find,match,sort等。流操作可以顺序执行也可以并行执行。

此外,流操作还有两个重要特点:

  • 流水线— 很多流操作本身会返回一个流,这样多个操作就可连接起来,形成一个流水线。流水线的操作可以看成对数据源进行数据库式操作。
  • 内部迭代— 与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

上图:

public class Dish {
    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;
    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    //省略getter和setter
}
public class Test {
	public static void main(String[] args) {

        List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));

        List<String> collect =  menu.stream() //从menu中获得流(菜肴列表)
                                    .filter(d -> d.getCalories() > 300) // 建立流水线操作,选出热量的菜肴
                                    .map(Dish::getName) //获取菜肴名
                                    .limit(3) // 只选出三个
                                    .collect(toList()); //  将结果保存在另一个集合中
        System.out.println(collect);
    }
}
输出:[pork, beef, chicken]

(看完这段代码注释后,你应该明白上面的那句:流水线的操作可以看成对数据源进行数据库式操作)

在本例中,我们先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果(它和别的操作不一样,因为它返回的不是流,在这里是一个List)。在调用collect之前,没有任何结果产生,实际上根本就没有从menu里选择元素。你可以这么理解:链中的方法调用都在排队等待,直到调用collect。

注意看,我们刚刚解释的这段代码,与逐项处理菜单列表的代码有很大不同。首先,我们使用了声明性的方式来处理菜单数据,即你说的对这些数据需要做什么:“查找热量最高的三道菜的菜名。”你并没有去实现筛选(filter)、提取(map)或截断(limit)功能,Streams库已经自带了。

1.2 流操作

可以连接起来的流操作称为中间操作,如filter,map,limit;
关闭流的操作称为中间操作,如collection。
中间操作
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

public class Test {
	public static void main(String[] args) {

        List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Dish.Type.MEAT),
                new Dish("beef", false, 700, Dish.Type.MEAT),
                new Dish("chicken", false, 400, Dish.Type.MEAT),
                new Dish("french fries", true, 530, Dish.Type.OTHER),
                new Dish("rice", true, 350, Dish.Type.OTHER),
                new Dish("season fruit", true, 120, Dish.Type.OTHER),
                new Dish("pizza", true, 550, Dish.Type.OTHER),
                new Dish("prawns", false, 300, Dish.Type.FISH),
                new Dish("salmon", false, 450, Dish.Type.FISH));

        List<String> collect = 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(collect);
    }
}

输出:
filtering--pork
mapping--pork
filtering--beef
mapping--beef
filtering--chicken
mapping--chicken
[pork, beef, chicken]

注意输出结果,并不是过滤出所有热量大于300的元素,然后再获取名称,再取前三个。而是在遍历时就将两个中间操作合并了。
在这里插入图片描述

2.使用流

流让我们从外部迭代转向内部迭代.可以使用支持filter和collection操作的StreamAPI管理对集合数据的迭代.只需要将筛选行为作为参数传递给filter方法.

2.1筛选和切片

  • 2.11 用谓词筛选
    Stream接口支持filter方法,该操作接受一个谓词(接收一个对象,返回boolean值的函数)作为参数,并返回一个包括所有符合谓词的元素的流.如:
    在这里插入图片描述

  • 2.12 筛选各异的元素
    流还支持一个distinct方法,返回一个元素各异(根据流所生成元素的equals和hashcode方法实现)的流.
    在这里插入图片描述

  • 2.13截短流
    流支持limit(n)方法,该方法会返回一个不超过给定长度的流.所需长度作为参数传递给limit.
    在这里插入图片描述* 2.14跳过元素
    流还支持skip(n)方法,该方法返回一个跳过前n个元素的流.如果流中元素不足n个,则返回空流.请注意limit(n)和skip(n)是互补的.
    在这里插入图片描述

2.2映射

一个非常常见的数据处理套路就是从某些对象中选择信息。Stream API也通过 map 和 flatMap 方法提供了类似的工具。

  • 2.21对流中每一个元素应用函数
    流支持map方法,它会接受一个函数作为参数,这个函数会被应用到每一个元素上,并将其映射成一个新的元素.(使用映射一词,是因为它和转换类似.但细微的差距是它是"创建一个新版本"而不是去修改).如:给定一个单词列表,返回每个单词有几个字母:
public static void main(String[] args) {
   List<String> strings = Arrays.asList("Lambdas", "jdk 8", "stream api");
   List<Integer> collect = strings.stream().map(String::length).collect(Collectors.toList());

   System.out.println(collect);
}

输出:
[7, 5, 10]

2.3查找和匹配

  • anyMatch (Predicate p) 检查"流中是否有一个元素匹配给定的谓词".返回一个boolean,是一个终端操作
  • allMatch(Predicate p) 工作原理和anyMatch类似.检查"流中所有元素是否都可以匹配给定谓词"
  • noneMatch(Predicate p),与allMatch相对,检查"流中没有一个元素与给定的谓词匹配"
    (anyMatch 、 allMatch 和 noneMatch 这三个操作都用到了我们所谓的短路,这就是大家熟悉的Java中 && 和 || 运算符短路在流中的版本。)
  • findAny(),返回当前流中的任意元素.返回一个Optional对象.关于Optional,后面会详细说明.
  • findFirst(),找到第一个元素.

2.4规约

需要将流中所有元素反复结合起来,得到一个值,比如一个 Integer 。这样的查询可以被归类为归约操作(将流归约成一个值).求和,最大,最小等.
举例来说:元素求和,在之前的方式:
在这里插入图片描述
现在:
在这里插入图片描述
使用reduce()方法,该方法包含两个参数: 一个初始值,一个Lambda来把两个流元素结合起来并产生一个新值.

示例:在这里插入图片描述

reduce 操作是如何对一个数字流求和的。首先, 0 作为Lambda( a )的第一个参数,从流中获得 4 作为第二个参数( b )。 0 + 4 得到 4 ,它成了新的累积值。然后再用累积值和流中下一个元素 5 调用Lambda,产生新的累积值 9 。接下来,再用累积值和下一个元素 3调用Lambda,得到 12 。最后,用 12 和流中最后一个元素 9 调用Lambda,得到最终结果 21 。
在这里插入图片描述

可以使用reduce的变体来求流中的最大或者最小元素:

Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Interger> min = numbers.stream().reduce((x,y) -> x < y ?x:y)

常见操作在这里插入图片描述

3.创建流

由之前的例子,我们已经能够使用stream()方法创建流,此外还有很多创建流的方式.

  • 3.1由值创建流
    你可以使用静态方法 Stream.of ,通过显式值创建一个流。它可以接受任意数量的参数。例如
    在这里插入图片描述
  • 3.2数组创建流,如
    在这里插入图片描述
  • 3.3 由函数生成流:无限流
    Stream API提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate 。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由 iterate和 generate 产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用 limit(n) 来对这种流加以限制,以避免打印无穷多个值。
  • iterate 方法接受一个初始值(在这里是 0 ),还有一个依次应用在每个产生的新值上的Lambda( UnaryOperator 类型)。如:
    在这里插入图片描述
  • generate,它接受一个 Supplier 类型的Lambda提供新的值。

4.总结

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值