Java JDK1.8 核心特性详解------Stream(流)的基本介绍

在前面的章节(Java JDK1.8 核心特性详解------Lambda表达式与方法引用),我们讲述了行为参数化以及Lambda表达式,在下面几篇文章里,我们会学习Stream的使用。


流是什么?

Stream流是 Java API的心成员,它允许你使用声明的方式处理数据集合。我们可以把流当作一种更加高级的迭代器。通过流我们可以更加方便的顺序或者并行的处理集合。下面将用例子让你先感受一下Stream的便利。假如我们要在一个List集合中筛选出年龄小于40岁的人,并且按照年龄进行排序,最后获取满足要求人的姓名。我们分别用传统方式和Stream的方式获取最终结果:

        //初始化数据
        List<People> peopleList = new ArrayList<>();
        peopleList.add(new People("张三", 15));
        peopleList.add(new People("李四", 41));
        peopleList.add(new People("赵六", 35));
        peopleList.add(new People("王五", 48));
        peopleList.add(new People("啊大", 14));
        peopleList.add(new People("啊二", 16));
----------------------------------------------------------------------------------------
        //普通方式
        List<People> peopleArrayList = new ArrayList<>();
        //筛选出年龄小于40岁的人
        for (People people : peopleList) {
            if (people.getAge() < 40) {
                peopleArrayList.add(people);
            }
        }
        //将人按照年龄从小到大排序
        peopleArrayList.sort(Comparator.comparing(People::getAge));
        //提取人的名字
        List<String> nameList = new ArrayList<>();
        for (People people:peopleArrayList) {
            nameList.add(people.getName());
        }

        List<People> peopleArrayList = new ArrayList<>();
        //筛选出年龄小于40岁的人
        for (People people : peopleList) {
            if (people.getAge() < 40) {
                peopleArrayList.add(people);
            }
        }
        //将人按照年龄从小到大排序
        peopleArrayList.sort(Comparator.comparing(People::getAge));
        //提取人的名字
        List<String> nameList = new ArrayList<>();
        for (People people:peopleArrayList) {
            nameList.add(people.getName());
        }

        System.out.println(nameList);
----------------------------------------------------------------------------------------
        //Stream方法
        List<String> nameList2 = peopleList.stream()
        //过滤出年龄小于40的人
        .filter(people1 -> people1.getAge() < 40)
        //按照年龄排序
        .sorted(Comparator.comparing(People::getAge))
        //获取名称
        .map(People::getName)
        //返回结果
        .collect(Collectors.toList());

        System.out.println(nameList2);

可以看到,在普通方法中,我们多创建了一个peopleArrayList 垃圾变量,同时,在代码量上也多了很多。除此之外,如果我们想并行处理集合,只需要把people.stream改成people.parallelStream,流会自动帮你使用并行的方式处理数据,这在多核CPU上会提升处理效率。目前来看,流有几个显而易见的好处:

  • 申明性:代码是以声明式方式写的:就像SQL语句一样,只是说明想完成什么,而不是说明如何实现。用这种方式加上行为参数化可以让我们很方便的应对需求

  • 可复合:我们可以把几个基础操作链接起来,来表达复杂数据的处理流水线,同时保证代码清晰可读

  • 可并行:我们可以简单的将集合并行处理,使性能更好。

流的简介

Stream相关类和接口在java.util.stream包中,我们可以通过很多方式获取到流,比如利用数值范围或者I/O资源生成流。流简短的定义就是“从支持数据处理操作的源生成的元素序列”。

  • 元素序列:和集合一样,流提供了一个接口,可以访问特定元素的一组有序值。因为集合时数据结构,所以它的主要目的是以特定的时间/空间复杂度储存和访问数据。但流的目的是表达计算。因此,集合讲的是数据,流讲的是计算
  • 源:流会从源中获取要处理的流,有序集合生成流时会保持原来的顺序。
  • 数据处理操作:对数据进行处理,包括过滤,排序,遍历等。流操作还可以顺序执行和并行执行。
  • 流水线:很多流操作会返回处理后的流,这样我们就可以将多个操作链接起来。
  • 内部迭代:流的迭代是在背后进行的,跟迭代器显式迭代不太一样。

流和集合的区别

什么时候进行读取和计算:

集合是一个内存中的数据结构,包含数据结构中目前所有的值,我们要先把所有的数据加载到集合中以后才可以对集合进行操作。就像我们我们用DVD看电影,包含电影的所有东西。要看必须要有完整的电影。流则是在概念上的固定的数据结构,其元素则是按需计算。例如我们在网上看电影,只需要加载目前进度的后面几分钟的电影就行。这样做有很大的好处,就是只有在我们需要的时候才会加载值。后面会有一个例子证明这点。

流只能遍历一次:

流和迭代器一样只能遍历一次,或者说只能消费一次。如果我们对一个流消费两次,那么代码会抛出java.lang.IllegalStateException:流已被操作或关闭。除非我们从数据源那再获取一个流。于此相反,我们可以对集合进行任意次的操作。

内部迭代和外部迭代:

我们使用集合或者迭代器的时候,需要我们自己去声明如何进行迭代,例如使用for循环或者是hasNext显性的去获取每一个元素,然后再在方法体中说明如何操作这些元素。这个就是外部迭代。对于流来说,你只需要申明对流进行什么操作。流会自动在内部对元素进行迭代。这就是内部迭代。

 流的操作

java.util.stream.Stream中的Stream接口定义了很多操作。它们可以分成两大类:中间操作和终端操作。回顾一下上面的例子,讲述两类的区别:

        List<String> nameList2 = peopleList
        //获取流
        .stream()
        //中间操作
        .filter(people1 -> people1.getAge() < 40)
        //中间操作
        .sorted(Comparator.comparing(People::getAge))
        //中间操作
        .map(People::getName)
        //终端操作
        .collect(Collectors.toList());

像filter、sorted、map等操作可以返回流,与其他操作连成一条流水线的流操作叫做中间操作。像collect这种会关闭流返回其他对象的操作叫做终端操作 

中间操作

中间操作会返回一个新的流,例如从peopleList.stream()产生的流,在经过filter()以后,会产生一个新的流,给sorted()使用,在sorted()使用以后会产生一个流给map。这样不断的传递下去,就相当于工厂的流水线。还有很重要的一点,除非流水线触发一个终端操作,不然中间操作不会执行任何处理。因为中间操作能够合并起来,在终端操作时一次性全部处理。

        List<String> nameList2 = peopleList.stream()
                //过滤出年龄小于40的人
                .filter(people1 ->{System.out.println("filter:"+people1.getAge());
                    return people1.getAge()<40;} )
                //获取名称
                .map(people -> {
                    System.out.println("map"+people.getName());
                    return people.getName();
                })
                //直取前三个
                .limit(2)
                //返回结果
                .collect(Collectors.toList());

这个代码将会打印:

filter:15
map张三
filter:41
filter:35
map赵六

 可以看出,流操作先将一个元素经过全部的中间操作以后,才会去执行另一个操作。这里利用了流的延迟性质。第一,尽管有很多人,但是我们只查询了前三个人就获得了我们要的结果。其次,我们将过滤和获取信息两个独立的操作合并到一次遍历中(可以更加清楚的看出我们的遍历过程,这个技术叫循环合并)。

终端操作

终端操作会从流水线中生成结果。返回结果可以是List,Integer,甚至是void。

使用流

一般来说,流的使用包括三个方面:

  • 从数据源中创建一个流

  • 对流进行中间操作

  • 对流进行终端操作

下面两张图是常用的流操作:

流当然还有更多的操作,以及许多模式,比如切片、查找、匹配等,会在后面慢慢说。


更多与JDK1.8相关的文章请看:Java JDK1.8 核心特性详解----(总目录篇)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值