浅谈java8中的流的使用

咱们在开发的过程当中会大量的使用集合,集合能够将数据进行分组,处理,好多的处理数据的业务逻辑相似于数据库的操做,好比说对一系列的实体根据它其中的某个属性来分组,筛选,像这样的操做,数据库是容许你声明式的指定这些操做的。好比说:java

SELECT name FROM apple WHERE weight < 400;
复制代码

这样的业务逻辑,咱们以前的代码实现都是for循环里面,填上一大堆的if判断,新建的临时变量,占用的代码空间很大,并且可读性也很差。数据库

List<Apple> appleList = new ArrayList<>();
        List<Apple> wantedAppleList = new ArrayList<>();
        for (Apple app : appleList) {
            if (app.getWeight() < 400) {   //筛选
                wantedAppleList.add(app);
            }
        }

        Collections.sort(wantedAppleList, new Comparator<Apple>() {
            public int compare(Apple d1, Apple d2) {  //排序
                return Long.compare(d1.getWeight(), d2.getWeight());
            }
        });

        List<Long> appleIdList = new ArrayList<>();
        for (Apple d : wantedAppleList) {
            appleIdList.add(d.getId());   //获取实体id
        }
复制代码

看上面的代码,占用的空间很大,并且还会产生和使用垃圾变量,好比代码中的appleIdList,它起到的做用只是一个一次性的中间容器。 在java8以后,这样的语句能够不让它出现了,你不须要担忧怎么去显式的实现如何筛选,你只须要说明你想要什么就好了。 若是要处理大量的元素,提升性能,你须要并行处理,利用多核架构,可是写并行代码更复杂,而调试起来也比较难受。好比说,使用synchronized来编写代码,这个代码是迫使代码顺序执行,也就违背了并行执行的初衷,这个在多核cpu上执行所需的成本会更大,多核的cpu的每一个处理器内核都有本身独立高速缓存,加锁须要把这些同步缓存同步进行,须要在内核间进行缓慢的缓存一致性协议通讯。 痛点说完了,接下来咱们说下java8中的流是怎么使用的。设计模式

java8中的流

java8中的集合支持一个新的Stream方法,它会返回一个流,到底什么是流呢? 流:从支持数据处理的源生成的元素序列。让咱们来咬文嚼字的来分别解释下,数组

  • 元素序列:和集合同样,流也提供了一个接口,能够访问特定元素类型的一组有序值,集合是一种数据结构,它的目的是存储和访问,可是流的目的是表达计算。
  • 源:固然就是提供数据的源头,大部分是集合、数组,由有序列表的产生的流顺序也是一致的。
  • 数据处理操做:流的数据处理功能很是相似于数据库的操做,就是表达出来你要怎么处理。

流也有两个重要的特色:浏览器

  • 流水线:Stream 中的不少操做会返回一个流,这样操做就能够连接起来,造成一个大的流水线。
  • 内部迭代:与集合自己的显式迭代不一样,Stream流的操做都是在背后进行的。 针对开头的代码,若是是java8的方式咱们应该怎么写呢?
//若是是多核架构的话,能够将stream()换成parallelStream()
List<Long> appleIdList = appleList
                .stream()
            //  .parallelStream()   并行处理
                .filter(apple -> apple.getWeight() < 400)
                .sorted(Comparator.comparing(Apple::getWeight))
                .map(Apple::getId)
                .collect(Collectors.toList());
复制代码

通过对比,思考,咱们能够发现,后者的代码是以声明的方式写的,就是陈述了你想要作什么,而不是一大堆的if、for的去实现。这样咱们再遇到别的需求的时候,不用再去复制代码了,你只要再按照这样的方式去陈述下你想要的就能够了,你能够把几个简单操做连接起来,来造成一个流水线,就表达复杂的数据处理。缓存

集合和流的内在区别

那么集合和流的内在区别是什么呢?bash

  1. 比较粗略的说,二者的主要区别就是在于什么时间进行计算。 集合是一个内存中的数据结构,它存储包含着全部的值,每个元素都是存放在内存里的,元素的计算结果才能成为集合的一部分。 而流呢,是概念上的固定的数据结构,元素都是按需计算的。从另外一个角度来讲,流就是延迟建立的集合,只要在须要的时候才会计算值,获得结果。套用管理学上的话:需求驱动,实时创造。 举一个例子:用浏览器进行搜索,当你输入一个关键字的时候,Google不会在全部的匹配结果都出来,全部的图片和都下载好以后才返回给你,而是首先给你10个或是20个,当你点击下一页的时候再给你接下来的10个,20个。这也就是只有在须要的时候才会去计算,好像有点相似于懒加载的意思。 有一点流和迭代器比较相似,就是流只能遍历一次,若是你还想在处理一遍,就只能从原始的数据源那里从新生成一个流来遍历(固然了,这里说的集合,不是I/O流)。数据结构

  2. 另外一个关键的区别就是二者的遍历数据的方式。 Collection接口须要用户去作迭代,for-each,就是外部迭代,去显式的取出每一个元素,去处理。而Stream使用的是内部迭代它把迭代已经作了,还把获得的流存储起来。内部迭代的时候,项目能够透明的并行处理,或者是用更好的顺序去处理,Stream库的内部迭代能够本身去选择一种适合你硬件的数据表示和并行实现。架构

流的操做:咱们再来看下上面的代码:filter、sorted、map流水线式的称为中间操做。collect触发流水线操做的是终端操做。app

流的使用

流的使用包括三件事:

  • 数据源,集合
  • 中间操做,流水线
  • 终端操做,执行流水线,生成结果

其实流水线的背后理念相似于构建器模式,构建器模式就是用来设置一套配置,也就是这里的中间操做,接着调用built方法,也就是这里的终端操做。关于设计模式,这里就不细说了,之后也会专门的说下各个设计模式,各位小伙伴不要捉急。

筛选 filter:筛选出符合条件的 distinct:去除重复 limit:返回一个不超过给定长度的流,截短 skip:跳过给定长度,若是超过总量,返回空

List<Apple> red = appleList.stream().filter(apple -> apple.getColor().equals("red")).distinct().limit(3).collect(Collectors.toList());
复制代码

map:对流的元素应用函数,接受一个函数做为参数,而且会把这个函数应用到每个元素上,并映射到一个新的元素。

List<String> appleNameList = appleList.stream().map(Apple::getName).collect(Collectors.toList());
复制代码

有的时候也会有这样状况,在使用map操做以后,会产生一个集合或者是数组,而你须要把全部的集合合并为一个集合,这被叫作流的扁平化,接着上代码:

List<String> collect = appleList.stream().map(Apple::getName).map(word -> word.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
复制代码

flatMap()方法让你把流中的每个值都换成另外一个流,而后把全部的流链接起来,成为一个流。

查找和匹配 anyMatch:流中是否有一个元素符合 allMatch:流中元素是否所有符合 noneMatch:流中无元素符合条件 findAny:查找当前流中的任意元素

Optional<String> any = appleList.stream().map(Apple::getName).map(word -> word.split("")).flatMap(Arrays::stream).distinct().findAny();
复制代码

Optional是一个容器,表明一个值存在值或不存在,这样就避免出现null了。能够用isPresent()方法判断这个容器是否有值。

归约:

//源码中的reduce
T reduce(T identity, BinaryOperator<T> accumulator);

Integer allSum = numbers.stream().reduce(0, (a, b) -> a + b);
复制代码

reduce()方法有两个参数,总和变量的初始值,上面的0就是,后面的Lambda是加和的操做。你也能够操做相乘,Lambda反复的结合每一个元素,一直到流被规约成一个值。 java8中Integer类如今有了一个静态的sum方法来求和,你还能够这么写:

Integer allInteger = numbers.stream().reduce(0, Integer::sum);
复制代码

关于reduce,它还有一个重载的变体,看下面,没有初始值,返回一个Optional对象,大家知道为何会是Optional吗?由于没有初始值,加和操做可能不会获得值。

Optional<Integer> result = numbers.stream().reduce(Integer::sum);
Optional<Integer> maxResult = numbers.stream().reduce(Integer::max);
Optional<Integer> minResult = numbers.stream().reduce(Integer::min);
复制代码

最后

若是对本文有任何异议或者说有什么好的建议,能够加我好友(公众号后台联系做者),也能够在下面留言区留言。但愿这篇文章能帮助你们披荆斩棘,乘风破浪。

这样的分享我会一直持续,你的关注、转发和好看是对我最大的支持,感谢。

相关文章
相关标签/搜索