Java 8 生成无限流(Infinite Streams)
本文我们学习 java.util.Stream API,将看到如何使用该构造来操作无限的数据/元素流。处理无限元素序列的可能性基于这样一个事实:流被构建为惰性的。这种惰性是通过将在流上执行的两种类型操作(中间操作和终止操作)分离来实现的。
1. 中间操作与终止操作
所有流操作分为中间操作与终止操作两类,并被组合为管道流形式。管道流由源(比如集合、数组,生成器函数,i/o channel,无限序列生成器),接着是零个或多个中间操作和一个终止操作。
1.1. 中间操作
中间操作只有终止操作执行时才执行。它们以管道流形式直播执行,可以通过下列方法加入管道流:
- filter()
- map()
- flatMap()
- distinct()
- sorted()
- peek()
- limit()
- skip()
所有中间操作是懒执行,即直到实际需要处理结果时才会执行。执行中间操作实际上并不执行任何操作,而是创建一个新的流,当遍历该流时,它包含与给定谓词匹配的原始流的元素。因此在执行管道的终止操作之前,流的遍历不会开始。
这是非常重要的特性,对于无限流尤其重要——因为它允许我们创建只有在调用终止操作时才实际调用的流。
1.2. 终止操作
终止操作可以遍历流生成结果或直接消费。终止操作执行后,可以认为管道流被消费了并不能再被使用。几乎在所有情况下,终端操作都是立即执行的,在返回之前完成对数据源的遍历和对管道的处理。
终止操作的立即性对无限流是重要概念,因为在处理时我们需要仔细考虑流是否被正确限制,举例,limit()操作。终止操作有:
- forEach()
- forEachOrdered()
- toArray()
- reduce()
- collect()
- min()
- max()
- count()
- anyMatch()
- allMatch()
- noneMatch()
- findFirst()
- findAny()
每个这些操作都会触发所有的中间操作。
2. 无限流
现在我们理解了两个概念————中间操作和终止操作,利用流的懒执行特性可以写无限流。
加入我们需要创建一个无限流,从0开始每次加2,并在调用终止操作之前调用限制方法。
在collect()终止方法之前使用limit()方法很重要,否则程序无限执行。
// given
Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 2);
// when
List<Integer> collect = infiniteStream
.limit(10)
.collect(Collectors.toList());
// then
assertEquals(collect, Arrays.asList(0, 2, 4, 6, 8, 10, 12, 14, 16, 18));
通过iterate()方法创建无限流,然后调用limit()方法限制数量,最后调用collect()终止方法。最终list结果中包括懒执行无限序列的前10个元素。
3. 自定义元素类型的无限流
我们现在创建一个随机UUID的无限流。第一个实现流API调用的生产者方法————生成随机值:
Supplier<UUID> randomUUIDSupplier = UUID::randomUUID;
通过generate()方法和生产者方法可以创建无限流:
Stream<UUID> infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);
然后我们可以从流中获取一组元素,需要记得的是当程序完成时使用limit()方法:
List<UUID> randomInts = infiniteStreamOfRandomUUID
.skip(10)
.limit(10)
.collect(Collectors.toList());
我们使用skip方法跳过前10个元素,取接下来的10个元素。这里通过generate()方法和生产者方法创建了一个自定义类型的无限流。
4. Do-While 流方式
请看简单DO…WHILE循环代码:
int i = 0;
while (i < 10) {
System.out.println(i);
i++;
}
程序打印i计数变量10次,这样结构很容易使用流方式实现。在流上没有doWhile方法,但使用limit方法可以实现:
Stream<Integer> integers = Stream
.iterate(0, i -> i + 1);
integers
.limit(10)
.forEach(System.out::println);
使用更少的代码实现相同功能,但是调用limit()方法并不像在流对象上使用doWhile()方法那样具有描述性。
总结
本文解释了如何使用流API创建无限流。当与limit()之类的转换方法一起使用时,可以使某些场景更容易理解和实现。懒执行在实际应用非常有用,结合无限流让问题变得简单。