Java 8 生成无限流(Infinite Streams)

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 infiniteStream = Stream.iterate(0, i -> i + 2);
 
// when
List 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 randomUUIDSupplier = UUID::randomUUID;

通过generate()方法和生产者方法可以创建无限流:

Stream infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

然后我们可以从流中获取一组元素,需要记得的是当程序完成时使用limit()方法:

List 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 integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

使用更少的代码实现相同功能,但是调用limit()方法并不像在流对象上使用doWhile()方法那样具有描述性。

总结

本文解释了如何使用流API创建无限流。当与limit()之类的转换方法一起使用时,可以使某些场景更容易理解和实现。懒执行在实际应用非常有用,结合无限流让问题变得简单。

你可能感兴趣的:(Java 8 生成无限流(Infinite Streams))