0%

stream and lambda(7) - stream 的创建

Stream 的创建,Stream 接口本身提供了一些方法来完成这个操作。而除此之外,JDK 本身也提供了其他类来完成这个操作。考虑到 Stream 的集合息息相关,合理猜测 ArraysCollectionMap 可能会提供创建 stream 的方法。但 Stream<T> 只有单泛型,而 Map<K,V> 是键值对,目测 Map 中不含有 Stream 相关的方法。来,大家可以看源码验证一下。

通过查看 JDK 源码发现, ArraysCollection 均提供 stream 相关的方法,而 Map 并未提供对应的方法。

Arrays

Arrays 的 stream 方法

从方法来看,Arrays 提供了多个重载方法,可以将 intlongdouble 以及实例对象数组转为 Stream,并且提供了截取数组区间的功能。如以下用例,就是将数组转成 stream。

1
2
3
4
5
6
public void arraysCreateTest() {
String[] strings = {"I", "am", "arrays", "demo"};
int[] nums = {1, 0, 0, 8, 6};
Arrays.stream(strings).forEach(System.out::println);
Arrays.stream(nums, 2, 5).forEach(System.out::println);
}

Collection

Collection 的 Stream 方法

Collection 提供的 stream()parallelStream() 方法已经在接口内实现,其子类,如 ListSet 可以直接使用。

1
2
3
4
5
public void collectionCreateTest() {
List<Integer> integers = Arrays.asList(1, 2, 9, 11, 5, 7, 3);
integers.stream().sorted().forEach(System.out::println);
integers.parallelStream().sorted().forEachOrdered(System.out::println);
}

Stream

empty

empty() 方法,返回的是一个空的 stream。既然没有任何元素,那这个 stream 的意义又在哪里?

不报 NPE 异常,还不够你臭屁的?要知道,java.util.Collections 还单独提供了 emptySet()emptyList() 方法呢。

来试一试,下面的代码能不能正常运行。

1
2
3
4
5
6
7
public void emptyTest(){
Stream<Integer> empty = Stream.empty();
System.out.println(empty.findFirst().orElse(-1));

empty = Stream.empty();
empty.forEach(System.out::println);
}

generate

generate 是个可以产生无尽流的方法。首先,我们先看看下面的代码的运行结果。

1
2
3
4
5
public void endlessGenerateTest() {
Stream<Integer> generate = Stream.generate(() -> new SecureRandom().nextInt());
AtomicLong al = new AtomicLong(0);
generate.forEach(integer -> System.out.println(al.incrementAndGet() + " : " + integer));
}

怎么样,让我听到你们电脑的欢呼声(此处应有滑稽表情)。好了,效果大家也看到了,我们来看下方法的定义:

static <T> Stream<T> generate(Supplier<T> s)

通过传入一个 Supplier,来生成一个 stream。生成内部元素的时候,每生成一个,就会向 Supplier 再要一个,如此下来产生的就是无尽流。

不过,也不用担心使用该方法后程序就停不下来,或者是 Supplier 产生了过量的元素导致性能低下。

1
2
3
4
5
6
7
8
public void limitGenerateTest() {
Stream<Integer> generate = Stream.generate(() -> {
System.out.println("#######");
return new SecureRandom().nextInt();
});
AtomicLong al = new AtomicLong(0);
generate.limit(3).forEach(integer -> System.out.println(al.incrementAndGet() + " : " + integer));
}

输出结果如下:

#######
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
2
3
4
5
6
7
8
9
10
11
public void ofTest() {
Stream<String> strStream = Stream.of("this is stream.of demo");
strStream.forEach(System.out::println);

Stream<Integer> intStream = Stream.of(1, 0, 0, 8, 6);
intStream.forEach(System.out::println);

List<String> list = Arrays.asList("i", "am", "list", "of", "method");
Stream<List<String>> listStream = Stream.of(list);
listStream.forEach(System.out::println);
}

注意:如果传入的参数是一个集合,如 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,将已有的两个流合并到一起。文档更明确的说了以下的点:

  1. 更准确的说,是将第二个流放到第一个流屁股后面。
  2. 如果两个流都是 ordered,那么得到的新流也是 ordered。
  3. 如果两个流有一个是并行流,则得到的新流是并行流。
  4. 关闭合并结果流时,会调用两个输入流的关闭方法。

当然,看源码的实现,上面的这些点是可以看出来的。抛开源码,我们来验证一下。

合成新流的元素顺序

1
2
3
4
5
public void concatTest1() {
Stream<Integer> first = Stream.of(1, 3, 5);
Stream<Integer> second = Stream.of(2, 3, 4);
System.out.println(Stream.concat(first, second).collect(Collectors.toList()));
}

经验证,输出的内容是:

[1, 3, 5, 2, 3, 4]

就这验证了,合成的流,顺序确实是第一个流 + 第二个流,并且不会去重

什么是 ordered

流是 ordered,并不是指对流的元素进行排序。而是指流内的元素,是有固定的顺序约束的。比如 List,元素是有约束顺序的:ArrayList 有数组坐标,LinkedList 有链表连接。所以,List 生成的流是 ordered。

更详细的内容,下次单独再说吧。

合并后的并行流

1
2
3
4
5
public void concatTest2() {
Stream<Integer> first = Stream.of(1, 3, 5).parallel();
Stream<Integer> second = Stream.of(2, 3, 4);
Stream.concat(first, second).map(i -> i + " ").forEach(System.out::print);
}

这次的测试,把第一个流换成了并行流。如果合并后的流不是并行,那么输出的内容应该是原定的顺序 1 3 5 2 3 4

验证后的结果却是输出是无序的,说明了合并后的流是并行流:

2 1 5 3 4 3

合并后流的关闭

1
2
3
4
5
6
public void concatTest3() {
Stream<Integer> first = Stream.of(1, 3, 5);
Stream<Integer> second = Stream.of(2, 3, 4);
System.out.println(Stream.concat(first, second).collect(Collectors.toList()));
Stream.concat(first, second).map(i -> i + " ").forEach(System.out::print);
}

为了验证这个问题,我们把流合并两次看一下。在执行的时候,实际抛了如下异常:

java.lang.IllegalStateException: stream has already been operated upon or closed

其实,从源码来看,在合并成新流之后,first 和 second 两个流都执行了 close 。

总结

Stream 的创建,ArraysCollection 也是出了一把力的。打铁还需自身硬,Stream 本身提供的这些创建方法,覆盖面也是极广的。毕竟出门靠朋友,Stream 就这么和集合打成一片了。

注:本文配套代码可在 github 查看:stream-and-lambda