Stream 的创建,Stream
接口本身提供了一些方法来完成这个操作。而除此之外,JDK 本身也提供了其他类来完成这个操作。考虑到 Stream 的集合息息相关,合理猜测 Arrays
,Collection
,Map
可能会提供创建 stream 的方法。但 Stream<T>
只有单泛型,而 Map<K,V>
是键值对,目测 Map
中不含有 Stream
相关的方法。来,大家可以看源码验证一下。
通过查看 JDK 源码发现, Arrays
、Collection
均提供 stream 相关的方法,而 Map
并未提供对应的方法。
Arrays
从方法来看,Arrays 提供了多个重载方法,可以将 int
、long
、double
以及实例对象数组转为 Stream
,并且提供了截取数组区间的功能。如以下用例,就是将数组转成 stream。
1 | public void arraysCreateTest() { |
Collection
Collection 提供的 stream()
和 parallelStream()
方法已经在接口内实现,其子类,如 List
和 Set
可以直接使用。
1 | public void collectionCreateTest() { |
Stream
empty
empty()
方法,返回的是一个空的 stream。既然没有任何元素,那这个 stream 的意义又在哪里?
不报 NPE 异常,还不够你臭屁的?要知道,java.util.Collections
还单独提供了 emptySet()
和 emptyList()
方法呢。
来试一试,下面的代码能不能正常运行。
1 | public void emptyTest(){ |
generate
generate
是个可以产生无尽流的方法。首先,我们先看看下面的代码的运行结果。
1 | public void endlessGenerateTest() { |
怎么样,让我听到你们电脑的欢呼声(此处应有滑稽表情)。好了,效果大家也看到了,我们来看下方法的定义:
static <T> Stream<T> generate(Supplier<T> s)
通过传入一个 Supplier
,来生成一个 stream。生成内部元素的时候,每生成一个,就会向 Supplier
再要一个,如此下来产生的就是无尽流。
不过,也不用担心使用该方法后程序就停不下来,或者是 Supplier
产生了过量的元素导致性能低下。
1 | public void limitGenerateTest() { |
输出结果如下:
#######
1 : -35704083#######
2 : -1875554696#######
3 : 541096732
可以看出来,使用 limit(3)
的时候,是每次需要时才产生新元素。
of
先看方法定义:
static <T> Stream<T> of(T t)
static <T> Stream<T> of(T… values)
好的食材,往往通过最简单的烹饪即可;而好用的方法,往往是一看就知道,一上手就会。of
方法,传入可变个数的参数,均可以转为 stream。
1 | public void ofTest() { |
注意:如果传入的参数是一个集合,如 List 或者 Set,流内的元素也是只有一个,而不是集合内的元素。流内的元素,就是那个集合。
iterate
iterate
听起来是不是有那么点熟悉?再想想,集合的迭代是怎么实现的?如果还没想到,再提示下,关键字 hasNext
。虽然集合有不少不用 Iterator
的遍历写法,但是这个迭代器还是要了解的。来先看下 iterate
方法定义:
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
其中,T seed
是第一个元素,后续元素通过迭代计算出来。这样滚动迭代,就产生了无限流。这点和 generate
其实有些相似,只不过 generate
的前后元素没有关联。
Stream.iterate(1, iter -> iter + 3).limit(5).forEach(System.out::print);
比如这个例子,将依次输出 1,4,7,10,13
。
concat
严格来说,concat
并不算是创建类型的,毕竟创建类型是从无到有,是创造;而 concat
站在了前人的肩膀上,是创新,将已有的两个流合并到一起。
照例,我们先看方法定义:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
concat,将已有的两个流合并到一起。文档更明确的说了以下的点:
- 更准确的说,是将第二个流放到第一个流屁股后面。
- 如果两个流都是 ordered,那么得到的新流也是 ordered。
- 如果两个流有一个是并行流,则得到的新流是并行流。
- 关闭合并结果流时,会调用两个输入流的关闭方法。
当然,看源码的实现,上面的这些点是可以看出来的。抛开源码,我们来验证一下。
合成新流的元素顺序
1 | public void concatTest1() { |
经验证,输出的内容是:
[1, 3, 5, 2, 3, 4]
就这验证了,合成的流,顺序确实是第一个流 + 第二个流,并且不会去重。
什么是 ordered
流是 ordered,并不是指对流的元素进行排序。而是指流内的元素,是有固定的顺序约束的。比如 List
,元素是有约束顺序的:ArrayList
有数组坐标,LinkedList
有链表连接。所以,List
生成的流是 ordered。
更详细的内容,下次单独再说吧。
合并后的并行流
1 | public void concatTest2() { |
这次的测试,把第一个流换成了并行流。如果合并后的流不是并行,那么输出的内容应该是原定的顺序 1 3 5 2 3 4
。
验证后的结果却是输出是无序的,说明了合并后的流是并行流:
2 1 5 3 4 3
合并后流的关闭
1 | public void concatTest3() { |
为了验证这个问题,我们把流合并两次看一下。在执行的时候,实际抛了如下异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
其实,从源码来看,在合并成新流之后,first 和 second 两个流都执行了 close 。
总结
Stream 的创建,Arrays
和 Collection
也是出了一把力的。打铁还需自身硬,Stream
本身提供的这些创建方法,覆盖面也是极广的。毕竟出门靠朋友,Stream
就这么和集合打成一片了。
注:本文配套代码可在
github
查看:stream-and-lambda