jdk8 Stream & BaseStream 源码难点浅析1

概述

  • 简要介绍 Stream 的 javadoc,与集合的关系以及区别,一些重要的注意事项
  • 难懂的 Stream 类的声明,介绍比较难懂的 onClose() 方法的调用

Stream的javadoc

Stream 的作用与特化版本

Stream 是对一个序列做 串行 或者 并行 聚合操作。他继承自 Stream
Stream 同时还有几个特化的版本 ,IntStream , LongStream, DoubleStream. 他们为了减少流操作的时候一些不必要的装箱和拆箱的操作。

需要注意的是,以上四个 Stream 是平行关系,他们都是继承自 BaseStream

举个简单的例子:

1
2
3
4
5
6
7
8
9
10
/**
* 将集合 widgets 中的元素中红色的元素的重量求和
* 第一步 filter() 过滤红色
* 第二步 mapToInt() 拿出所有的Red的重量,生成一个 IntStream
* 第三步 sum() 求和,并返回
*/
int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();

Stream 的组成与特点

要想实现计算,所有操作,都会被放入一个 pipeline 当中(类似liunx)中的操作。

而一个流管道(pipeline)包含:

  • source(源): 数组,集合,迭代器,I/O 操作等等
  • 0个或者多个中间操作: 将一个流转成另外一个流
  • 1个终止操作 : 产生一个结果(比如上面的例子中的求和) or 或者修改传入对象的属性。

流是Lazy的懒惰的!! 你不加 终止操作 流的操作,就不会被执行。
如上面的例子,没有最后的 Sum() 函数,前面的操作是不会被执行的。

集合和Stream 的区别

集合: 注重存储,主要考虑元素的访问与管理
Stream :注重计算,主要考虑以一种描述性 的语言来对源进行一系列的操作,并将操作聚合起来。

流的注意事项

1 流中的操作,都应是函数式接口(lambda表达式或者方法引用)
2 流不能被重用,每个流只能应用一次。想再次操作你需要重新生成一个流

3 流虽然实现了 AutoCloseable 接口,但是几乎所有的流都是不用关闭的,因为他的源大部分情况下都是集合,而集合是不用关闭的。

除非源是一个 I/O Channel。 比如 Files.lines() 方法。
如果是这样,Stream 就可以申明在 try-with-resources block 中。
关于这个 j7 新接口的使用,可以看我之前的一篇关于 AutoCloseable 的介绍 jdk1.7新增自动关闭接口AutoCloseable

多说一句,Files.lines() 是 j8 中新增的方法,有点类似 pythonreadlines() 方法。很好用。

Stream & BaseStream 解析

看不懂的声明

我们可以看一下 Stream 以及 BaseStream 的类的声明

1
2
3
4
//Stream
public interface Stream<T> extends BaseStream<T, Stream<T>>
//BaseStream
public interface BaseStream<T, S extends BaseStream<T, S>>

BaseStream 中,第二个泛型是 S extends BaseStream<T, S> ,而 Stream 中,第二个泛型是 Stream<T>, 正好是满足 extends BaseStream<T, S> 的条件的,所以可以这么写。

两个泛型有没有感觉有点晕。其实比较好理解。

  • T 这个泛型很好理解,就是流中元素的类型
  • S

如果你看过javadoc的描述,流的所有中间操作,都会返回一个流,而这个S 就代表着中间操作返回的流的类型。
比如我们看一下这个在 Stream 中的方法(其他方法与其一致)

image_1bf3j7bv61mi710k61td11d8l5o79.png-124.2kB

难懂的 onClose() 方法

上面我们知道 BaseStream 实现了 AutoCloseable 接口,也就是 Close() 方法可以得到调用。但是 BaseStream 中,给我们提供了要给OnClose() 方法。我们看下截图

image_1bf3kmgk61641d2k1euk992t28m.png-165.3kB

这个方法,就是当 Close() 方法被调用的时候 onClose()会被调用。但是有几个注意的点

  • onColse() 方法也返回一个流,也就是说可以多次调用。
  • 如果你写了多个onClose() 方法,它会按照顺序调用。
  • 前一个 onClose() 方法除了异常不影响后续 onClose 方法的使用
  • 如果多个 onClose() 方法都抛出异常,只展示第一个异常的堆栈,而其他异常会被压缩,只展示部分信息

上代码!!!!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Created by charleszhu on 2017/5/2.
*/
public class StreamTestOnCloseTest {
public static void main(String[] args) {
List<String> testString = Arrays.asList("min", "ge", "da", "shuaibi");
/**
* 两次关闭
* 两次异常
* 预期:两次关闭输出依次打出;两次关闭异常只打印一次堆栈,另外一次只展示
*/
try(Stream<String> stream = testString.stream()) {
stream.onClose(() ->{
System.out.println("closing 1"); //
throw new NullPointerException("exception 1");
}).onClose(() -> {
System.out.println("closing 2");
throw new NullPointerException("exception 2");
}).forEach(System.out::println);
}
}
}

结果如下图:

image_1bf3lgle8tfjq0nqm2jjd17hk13.png-149.2kB

你们明白了吧(我是天才)

好懂的其他方法

BaseStream 中有的一些其他方法,除了分割迭代器(后面会单独说,因为太重要)

1
Spliterator<T> spliterator();

其他都比较好懂。值得一说的是以下两个方法:

1
2
S sequential(); // 返回串行流
S parallel(); // 返回并行流

你可以看到他们都返回流对象,也就是可以继续调用 sequential 或者 parallel 方法。但是!!!

无论你中间怎么调用 ,比如 sequential.parallel.parallel.sequential….

只以最后一个为最终流的类型!!!

朱老师&敏哥 wechat
有惊喜,朋友🙄
我要拿铁不加糖.