Java 8 and Infinite Streams – Java 8和无限流(Infinite Streams

最后修改: 2017年 2月 12日

1. Overview

1.概述

In this article, we will be looking at a java.util.Stream API and we’ll see how we can use that construct to operate on an infinite stream of data/elements.

在这篇文章中,我们将看到一个java.util.Stream API,我们将看到我们如何使用该结构来操作无限的数据/元素的流。

The possibility of working on the infinite sequence of elements is predicated on the fact that streams are built to be lazy.

在元素的无限序列上工作的可能性是以流被构建为懒惰的事实为前提的。

This laziness is achieved by a separation between two types of the operations that could be executed on streams: intermediate and terminal operations.

这种懒惰性是通过分离两种可在流上执行的操作来实现的。中间终端操作。

2. Intermediate and Terminal Operations

2.中级和终端操作

All Stream operations are divided into intermediate and terminal operations and are combined to form stream pipelines.

所有的Stream操作被分为中间终端操作,并被组合成流管道。

A stream pipeline consists of a source (such as a Collection, an array, a generator function, an I/O channel, or infinite sequence generator); followed by zero or more intermediate operations and a terminal operation.

一个流管道由一个源(如一个Collection、一个数组、一个生成器函数、一个I/O通道或无限序列生成器)组成;然后是零个或多个中间操作和一个终端操作。

2.1. Intermediate Operations

2.1.中级操作

Intermediate operations are not executed until some terminal operation is invoked.

中间操作不被执行,直到一些终端操作被调用。

They’re composed forming a pipeline of a Stream execution. The intermediate operation can be added to a Stream pipeline by methods:

它们的组成形成了一个Stream执行的流水线。intermediate操作可以通过方法添加到Stream管道中。

  • filter()
  • map()
  • flatMap()
  • distinct()
  • sorted()
  • peek()
  • limit()
  • skip()

All Intermediate operations are lazy, so they’re not executed until a result of a processing is actually needed.

所有的Intermediate操作都是懒惰的,所以它们不会被执行,直到真正需要一个处理结果。

Basically, intermediate operations return a new stream. Executing an intermediate operation does not actually perform any operation, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate.

基本上,中间操作返回一个新的流。执行中间操作实际上并不执行任何操作,而是创建一个新的流,该流在被遍历时,包含初始流中与给定谓词相匹配的元素。

As such, traversal of the Stream doesn’t begin until the terminal operation of the pipeline is executed.

因此,在执行管道的终端操作之前,对Stream的遍历不会开始。

That is very important property, specifically important for infinite streams – because it allows us to create streams that will be actually invoked only when a Terminal operation is called.

这是非常重要的属性,对无限流特别重要–因为它允许我们创建只有在Terminal操作被调用时才会实际调用的流。

2.2. Terminal Operations

2.2.终端操作

Terminal operations may traverse the stream to produce a result or a side effect.

终端操作可以遍历流,以产生一个结果或副作用。

After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.

在执行终端操作后,流管道被认为是被消耗的,不能再被使用。几乎在所有情况下,终端操作都是急切的,在返回之前完成对数据源的遍历和对流水线的处理。

The eagerness of a terminal operation is important concerning infinite streams because at the moment of processing we need to think carefully if our Stream is properly bounded by, for example, a limit() transformation. Terminal operations are:

终端操作的急切性关于无限流是很重要的,因为在处理的时刻,我们需要仔细思考我们的Stream是否被适当的约束,例如,一个limit()变换。Terminal操作是。

  • forEach()
  • forEachOrdered()
  • toArray()
  • reduce()
  • collect()
  • min()
  • max()
  • count()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • findFirst()
  • findAny()

Each of these operations will trigger execution of all intermediate operations.

这些操作中的每一个都将触发所有中间操作的执行。

3. Infinite Streams

3.无限的溪流

Now that we understand these two concepts – Intermediate and Terminal operations – we’re able to write an infinite stream that leverage laziness of Streams.

现在我们理解了这两个概念–中级终端操作–我们能够编写一个无限的流,利用流的懒惰性。

Let’s say that we want to create an infinite stream of elements from zero that will be incremented by two. Then we need to limit that sequence before calling terminal operation.

比方说,我们想从零开始创建一个无限的元素流,这个元素流将被递增为二。那么我们需要在调用终端操作之前限制该序列。

It is crucial to use a limit() method before executing a collect() method that is a terminal operation, otherwise our program will run indefinitely:

在执行作为终端操作的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));

We created an infinite stream using an iterate() method. Then we called a limit() transformation and a collect() terminal operation. Then in our resulting List, we will have first 10 elements of an infinite sequence due to a laziness of a Stream.

我们使用iterate() 方法创建了一个无限的流。然后我们调用了一个limit() 转换和一个collect() 终端操作。然后在我们生成的List中,由于Stream的懒惰,我们将拥有一个无限序列的前10个元素

4. Infinite Stream of a Custom Type of Elements

4.一个自定义类型的元素的无限流

Let’s say that we want to create an infinite stream of random UUIDs.

假设我们想创建一个无限的随机UIDs流。

The first step to achieving this using Stream API is to create a Supplier of those random values:

使用Stream API实现这一目标的第一步是创建这些随机值的Supplier

Supplier<UUID> randomUUIDSupplier = UUID::randomUUID;

When we define a supplier we can create an infinite stream using a generate() method:

当我们定义一个供应商时,我们可以使用generate()方法创建一个无限的流。

Stream<UUID> infiniteStreamOfRandomUUID = Stream.generate(randomUUIDSupplier);

Then we could take a couple of elements from that stream. We need to remember to use a limit() method if we want our program to finish in a finite time:

然后我们可以从该流中提取几个元素。如果我们希望我们的程序在有限的时间内完成,我们需要记住使用limit() 方法。

List<UUID> randomInts = infiniteStreamOfRandomUUID
  .skip(10)
  .limit(10)
  .collect(Collectors.toList());

We use a skip() transformation to discard first 10 results and take the next 10 elements. We can create an infinite stream of any custom type elements by passing a function of a Supplier interface to a generate() method on a Stream.

我们使用一个skip() 转换来丢弃前10个结果,并取下后10个元素。我们可以通过向Stream上的generate()方法传递Supplier接口的函数来创建一个任何自定义类型元素的无限流。

6. Do-While – the Stream Way

6.Do-While – the Stream Way

Let’s say that we have a simple do..while loop in our code:

比方说,我们的代码中有一个简单的do…while循环。

int i = 0;
while (i < 10) {
    System.out.println(i);
    i++;
}

We are printing i counter ten times. We can expect that such construct can be easily written using Stream API and ideally, we would have a doWhile() method on a stream.

我们正在打印i counter十次。我们可以期待使用Stream API很容易写出这样的结构,理想情况下,我们会在一个流上有一个doWhile() 方法。

Unfortunately, there is no such method on a stream and when we want to achieve functionality similar to standard do-while loop we need to use a limit() method:

不幸的是,在流中没有这样的方法,当我们想实现类似于标准do-while循环的功能时,我们需要使用limit()方法。

Stream<Integer> integers = Stream
  .iterate(0, i -> i + 1);
integers
  .limit(10)
  .forEach(System.out::println);

We achieved same functionality like an imperative while loop with less code, but call to the limit() function is not as descriptive as it would be if we had a doWhile() method on a Stream object.

我们用更少的代码实现了与命令式while循环相同的功能,但对limit()函数的调用并不像我们在Stream对象上的doWhile()方法那样具有描述性。

5. Conclusion

5.结论

This article explains how we can use the Stream API to create infinite streams. These, when used together with transformations such as limit() – can make some scenarios quite a bit easier to understand and implement.

这篇文章解释了我们如何使用Stream API 来创建无限的流。当与诸如limit() – 这样的转换一起使用时,可以使一些场景更容易理解和实现。

The code supporting of all these examples can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

所有这些例子的代码支持都可以在GitHub项目中找到–这是一个Maven项目,所以应该很容易导入并按原样运行。