软件架构师Java学习笔记程序员

Java8 源码阅读之——Stream

2017-05-06  本文已影响1059人  石马木寸

** 写在前面的话:本人作为一枚纯技术爱好者,一直喜欢利用闲暇写一点自己研究的收获和体会。虽然力求谨慎,但个人见解难免会有有失偏颇的时候,还望各位读者批评指正! **

Stream的继承关系

要想吃透Stream的设计,最好还是从它的UML设计图开始。通过上图我们可以看到,貌似BaseStream是作为核心流操作的规范存在,那么我们就先以它为切入点开始剖析整个Stream的体系。

AutoCloseable接口

但在BaseStream之上仍然有一个AutoCloseable接口,虽然这个接口很简单,但我们还是先看一下吧。

This construction ensures prompt release, avoiding resource exhaustion exceptions and errors that may otherwise occur.

这段截取自该接口的注释,大致意思就是这个结构保证了资源的及时释放,避免发生资源枯竭带来的异常。

Stream顾名思义“流”,这个流可能来自文件、网络I/O……所以JDK在顶层定义了这样一个接口,规定了流的实现类在资源使用完成或者遇到异常的时候及时释放资源,避免发生资源枯竭。

所以这个接口也只声明了一个方法: void close() throws Exception 也就是关闭流操作。

BaseStream 接口

现在正式开始介绍BaseStream,我们先来大致看一下它都定义了哪些规范。

Iterator<T> iterator()

定义了每个Stream都需要提供返回一个迭代子对象的方法。

Spliterator<T> spliterator()

和iterator() 方法类似,但返回的对象是Spliterator而不是Iterator。Spliterator可以将元素分割成多份,分别交于不于的线程去遍历,以提高效率。

使用Iterator的时候,我们可以顺序地遍历容器中的元素,使用Spliterator的时候,我们可以将元素分割成多份,分别交于不于的线程去遍历,以提高效率。

boolean isParallel()

判断该流是不是并行执行。

S sequential()

返回一个串行流,如果该流本身就是串行的,则返回他本身。

S parallel()

返回一个并行流,如果该流本身就是并行的,则返回他本身。

S unordered()

返回一个无序流,如果该流本身就是无序的,则返回他本身。

S onClose(Runnable closeHandler)

返回一个带着关闭处理的流。closeHandler线程将在调用close()方法时执行。

乍一看定义的方法很多,但实际上只有三类。第一类是iterator() 和 spliterator()操作提供了遍历元素的方法。

第二类是 sequential()、parallel()、unordered()对流进行转化,返回一个指定的流。

第三类就是对关闭流方法的处理。

总结来讲,BaseStream并没有对具体流的操作做任何说明,只是规定了三种流的形式和提供了两种遍历流的方式——单线程、多线程两种遍历方式。

最后再提一下关于BaseStream这个接口的定义: interface BaseStream<T, S extends BaseStream<T, S>>这个定义方式也是很奇特——泛型的第二个参数必须是自己的子类。

看了JDK关于T和S的解释,T是指定stream元素的类型,S是stream的实现类。

看完也就理解了,它使用S规定了创建流的类型,而这个接口有4个方法会返回指定流,所以才需要规定流的类型必须是BaseStream的实现类。

Stream 接口

实际上BaseStream并没有定义任何与具体流的操作,所以我们只能把目光转向它的子接口。 BaseStream 接口下面有4个继承接口和一个抽象类,之所以选Stream接口是因为从命名上来看IntStream、LongStream、DoubleStream与是作为特殊数据特殊处理的Stream而存在的,所以我们姑且可以先看Stream接口。

这篇文章的目的是拆解分析整个Java8 Stream体系,过多介绍某个接口或实现类的方法并没有太大的意义,所以挑了几个比较具有代表性的方法。

Stream<T> filter(Predicate<? super T> predicate)

该方法接收一个lambda表达式(Predicate类型)过滤出符合条件的元素,并返回一个新流。

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

该方法接收一个lambda表达式(Function类型)过滤出符合条件的元素,并返回一个新流。

T reduce(T identity, BinaryOperator<T> accumulator)

该方法接收一个lambda表达式(BiFunction类型)过滤出符合条件的元素,并返回结果。

void forEach(Consumer<? super T> action)

和以上方法类似,传入一个lambda表达式(Consumer类型),遍历流,无返回值。

这些接口看着差别很大,但仔细研究一下就会发现,他们都是传入一个Lambda表达式,这个表达式会对流的元素进行一定操作,只是返回值有差异而已。filter、map是返回一个新流,reduce是返回结果,forEach是对元素遍历。

对比于List接口定义的一些抽象——get、set、add、remove……,我们可以发现传统的Collection的关注点在元素,而Stream的关注点在于函数,更确切的讲是对数据的计算。所以我们认为Stream的出现是对Collection的一种补充。

一些新的概念

JDK的作者有一个很好的习惯,就是在重要的接口定义上会有非常详细的注释。Stream也不例外,一个简单的Stream有着100多行的注释。

随着对注释的解读,我发现作为对Collection的补充,Stream不仅有着很便捷的语法,对大数据量的处理也有更高的效率,以下是本人对一些比较重要的特点的一些总结。

相对的 terminal operation 对应的操作就是那些产生了一定结果或者其他影响的操作,例如: Stream#count()} 或者 Stream#forEach(Consumer)}

为什么JDK会把stream的操作区分为以上两种形式呢?原因他们也给了解释: Streams are lazy; computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.

Streams 是懒加载的,仅在启动terminal操作时才执行源数据的计算,并且仅根据需要消耗源元素。

总结:

*** To be continue……***

** 关于文章开头UML图中的XXXPipeline也是非常重要且优雅的设计,我会在下一章揭开它的神秘面纱。**

上一篇下一篇

猜你喜欢

热点阅读