• java8学习之Stream介绍与操作方式详解


    关于默认方法【default method】的思考:

    在上一次【http://www.cnblogs.com/webor2006/p/8259057.html】中对接口的默认方法进行了学习,那在Java8中在接口中接出默认方法是为了解决什么问题或者说规避什么问题呢?凡是在JDK引入一个新的概念肯定是专家们经过了一定的权衡才加入的,那加入默认方法的具体原因是啥呢?其实主要是为了解决Java8的向后兼容问题,因为毕境如今Java是一个历史非常优久且用JAVA老版本的系统也非常非常之多了,不可能因为升级到了JAVA8之后由于加入了某种新特性就造成老的系统用不了了,下面拿咱们之前使用过的List接口里面的在Java8中新加入的sort()方法来理解一下这个“向后兼容”,如下:

    也就意味着在List接口中带了一个排序的功能了,那假设不是以这种默认方法的形式来提供的话【也就是将sort()方法声明为抽象的】,那么对于以前使用了List接口的代码那就会造成破坏性的影响,为什么?可以想象一样对于一个稳定的老系统当从Java的低版本升级到了Java8之后,发现List接口中多出了一个sort()抽象方法,那不得对稳定的老系统修改代码来保证可以兼容Java8,那这种设计是完全不合理的,合理的设计应该是当一个系统Java由低版本升到高版本之后应该是完全可以正常编译运行的,那么默认方法的出现就完全可以解决这样向后兼容的问题了,因为它是具体实现所以也不会使用者强制去重写里面的代码,对于老版本系统而言也无需做任何的代码变更,所以光知道默认方法是个什么东东这个不是重点,重点是要体会为啥要提出默认方法这个东东。

    Stream介绍与操作方式:

    关于Stream在之前的学习中也已经初步涉及到了,如下:

    只是木有系统的学习,接下来就系统的对它进行了解,Stream是在Java8中与Lambda表达式相伴相生的一个极其重要的概念,通过流的方式可以让我们更好的操作集合,而且以函数式、更加流畅、更加语义化的来让我们去操作流,而流中会大量的使用到咱们之前学习的函数式接口,我们知道在Java8中函数式接口都位为java.util.function包中,而对于流则全位于java.util.stream包中,如下:

    展开流包看一下:

    AbstractPipeline
    AbstractShortCircuitTask
    AbstractSpinedBuffer
    AbstractTask
    BaseStream
    Collector
    Collectors
    DistinctOps
    DoublePipeline
    DoubleStream
    FindOps
    ForEachOps
    IntPipeline
    IntStream
    LongPipeline
    LongStream
    MatchOps
    Node
    Nodes
    PipelineHelper
    ReduceOps
    ReferencePipeline
    Sink
    SliceOps
    SortedOps
    SpinedBuffer
    Stream
    StreamOpFlag
    Streams
    StreamShape
    StreamSpliterators
    StreamSupport
    TerminalOp
    TerminalSink
    Tripwire

    可以看到非常之多,当然不可能一一学到,这里跟函数式接口的学习方法类似,只学重要的,当重要的都掌握了之后,其它的就触类旁通啦,另外Stream是流中最重要的基础,如标红所示,所以接下来先学它,打开它查看一下它里面的方法,大多数是高阶函数【具体什么是高阶函数可以参考:http://www.cnblogs.com/webor2006/p/8193855.html】:

    接下来读一下它的javadoc:

    接着看官方对这个示例的解释:

    说到pipeline,其实跟Linux的管道的概念很像,管道是将多个操作连接在一起,使得多个简单的操作最后形成一个功能非常强大的操作。

    这个说明比较重要,说明了流操作的构成,这里先有个印象,之后还会再解释。

    根据这段关键的说明可以对流进行一个总结:

    1、流由三部份组成:

    ①、源;

    ②、零个或多个中间操作;

    ③、终止操作;

    2、流操作的分类:

    ①、惰性求值;

    ②、及早求值;

    怎么理解上面列的总结呢?其中"惰性求值"实际上就是指的"零个或多个中间操作","及早求值"指的是"终止操作",比如说有这样一个流操作:

    "stream.xxx().yyy().zzz().count();",其中stream为流所对应的源,而xxx().yyy().zzz()则为中间操作,它们就是惰性求值的;而count()则为终止操作,它是及早求值的。惰性顾名思义就是比较懒惰的,如果没有终止操作它们是不会立马执行的,也就是说"stream.xxx().yyy().zzz()"这句话因为没有终止操作啥都不会执行,这就证明确实是懒性滴,而只要有了终止操作count(),那中间操作才会进行执行,而且是立即执行,并且流操作中只有一个终止操作,目前没有代码的描述可能比较抽象,有个印象既可。

    流的创建:

    接下来编写代码来领略一下流的使用,首先看一下如何创建一个流:

    而其中看一下of()方法的定义:

    接下通过数组创建,如下:

    这两种其本质上是一样的,都是调用of()方法来创建。

    接下来继续用第三种方式创建:

    其实这种方式跟第一种的本质还是一样的,为什么?

    最后再看一下最常用的创建流的方式,如下:

    流的使用:

    接下来具体使用一下流,看能给咱们带来哪些便利性:

    编译运行:

    为啥是包含了3,但是不包含8呢?看下range()的具体实现:

    另外从这个例子中有木有体会到使用了Stream之后的便捷性,如果照传统的方式生成那得弄一个循环了,而使用了Stream之后一句话搞定。

    那如果也想包含后面的元素呢?其实也有现成的方法,如下:

    其看一下该方法的参数名就可以知晓确实是包含结束元素:

    接下来用Stream来实现如下场景:有一个整形类型的List,然后对List中的每一个元素x2,然后再将所有的乘2的元素求和输出出来。传统的实现方式肯定得要遍历去实现,这里就不演示了,直接采用新的方式来写:

    接着就是需要对其元素进行求和了,那怎么做呢?这时得用到reduce()了,如下:

    其中第一个参数identity为初始累加值,这里当然传0啦,第二个参数为BinaryOperator,这个函数式接口咱们之前已经学习过了,回顾下它的函数原型:

    而在Integer类中对于两数求和的函数刚好满足这个函数式接口的要求,看:

    所以其最终的写法如下:

    可见用流的方式实现是何等的清晰简单,而且语义上也更好理解,针对这个流操作,再来复习一下它的组成部分:

  • 相关阅读:
    百度地图API-自定义图标覆盖物
    笔记-前端学习路线
    当div自适应的高度超过预设的高度的时候出现滚动条的办法
    有关前端的书籍
    js-方法
    正在进行中
    ARM的启动和中断向量表
    ARM中的总线
    NOR FLASH与NAND FLASH的区别
    ARM流水线关键技术分析与代码优化
  • 原文地址:https://www.cnblogs.com/webor2006/p/8280749.html
Copyright © 2020-2023  润新知