Project Reactor: map() vs flatMap() – Project Reactor: map() vs flatMap()

最后修改: 2021年 6月 3日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This tutorial introduces the map and flatMap operators in Project Reactor. They’re defined in the Mono and Flux classes to transform items when processing a stream.

本教程介绍了Project Reactor中的mapflatMap操作符。它们被定义在MonoFlux类中,以便在处理流时转换项。

In the following sections, we’ll focus on the map and flatMap methods in the Flux class. Those of the same name in the Mono class work just the same way.

在下面的章节中,我们将重点讨论mapflatMap方法在Flux类中的作用。那些在Mono类中的同名方法的工作方式也是如此。

2. Maven Dependencies

2.Maven的依赖性

To write some code examples, we need the Reactor core dependency:

为了编写一些代码示例,我们需要Reactor核心依赖性

<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.3.9.RELEASE</version>
</dependency>

3. The map Operator

3.map操作符

Now, let’s see how we can use the map operator.

现在,让我们看看如何使用map操作符。

The Flux#map method expects a single Function parameter, which can be as simple as:

Flux#map方法期望有一个Function参数,它可以是简单的。

Function<String, String> mapper = String::toUpperCase;

This mapper converts a string to its uppercase version. We can apply it on a Flux stream:

这个映射器将一个字符串转换为其大写版本。我们可以在一个Flux流上应用它。

Flux<String> inFlux = Flux.just("baeldung", ".", "com");
Flux<String> outFlux = inFlux.map(mapper);

The given mapper converts each item in the input stream to a new item in the output, preserving the order.

给定的映射器将输入流中的每个项目转换为输出中的新项目,保留顺序

Let’s prove that:

让我们来证明这一点。

StepVerifier.create(outFlux)
  .expectNext("BAELDUNG", ".", "COM")
  .expectComplete()
  .verify();

Notice the mapper function isn’t executed when the map method is called. Instead, it runs at the time we subscribe to the stream.

请注意,在调用map方法时,并没有执行mapper函数。相反,它在我们订阅流的时候运行

4. The flatMap Operator

4.flatMap操作符

It’s time to move on to the flatMap operator.

现在是时候转向flatMap操作符了。

4.1. Code Example

4.1.代码示例

Similar to map, the flatMap operator has a single parameter of type Function. However, unlike the function that works with map, the flatMap mapper function transforms an input item into a Publisher rather than an ordinary object.

map类似,flatMap操作符有一个Function类型的单一参数。然而,与使用map的函数不同,flatMap映射器函数将输入项转化为Publisher而不是普通对象。

Here’s an example:

这里有一个例子。

Function<String, Publisher<String>> mapper = s -> Flux.just(s.toUpperCase().split(""));

In this case, the mapper function converts a string to its uppercase version, then splits it up into separate characters. Finally, the function builds a new stream from those characters.

在这种情况下,映射器函数将一个字符串转换为其大写版本,然后将其分割成独立的字符。最后,该函数从这些字符中建立了一个新的流。

We can now pass the given mapper to a flatMap method:

现在我们可以将给定的映射器传递给flatMap方法。

Flux<String> inFlux = Flux.just("baeldung", ".", "com");
Flux<String> outFlux = inFlux.flatMap(mapper);

The flat-mapping operation we’ve seen creates three new streams out of an upstream with three string items. After that, elements from these three streams are split and intertwined to form another new stream. This final stream contains characters from all three input strings.

我们看到的扁平映射操作从一个有三个字符串项的上游创建了三个新流。之后,这三个流中的元素被分割并交织在一起,形成另一个新流。这个最终的流包含所有三个输入字符串的字符。

We can then subscribe to this newly formed stream to trigger the pipeline and verify the output:

然后我们可以订阅这个新形成的流,以触发管道并验证输出。

List<String> output = new ArrayList<>();
outFlux.subscribe(output::add);
assertThat(output).containsExactlyInAnyOrder("B", "A", "E", "L", "D", "U", "N", "G", ".", "C", "O", "M");

Note that due to the interleaving of items from different sources, their order in the output may differ from what we see in the input.

请注意,由于来自不同来源的项目的交错,它们在输出中的顺序可能与我们在输入中看到的不同

4.2. Explanation of the Pipeline Operations

4.2.管线作业的解释

We’ve just gone through defining a mapper, passing it to a flatMap operator, and invoking this operator on a stream. It’s time to dive deep into the details and see why items in the output may be out of order.

我们刚刚经历了定义映射器,将其传递给flatMap运算器,并在流中调用该运算器。现在是时候深入了解细节了,看看为什么输出中的项目可能不符合顺序。

First, let’s be clear that no operations occur until the stream is subscribed. When that happens, the pipeline executes and invokes the mapper function passed to the flatMap method.

首先,我们要清楚,在流被订阅之前,没有操作发生。当这种情况发生时,流水线会执行并调用传递给flatMap方法的映射器函数。

At this point, the mapper performs the necessary transformation on elements in the input stream. Each of these elements may be transformed into multiple items, which are then used to create a new stream. In our code example, the value of the expression Flux.just(s.toUpperCase().split("")) indicates such a stream.

在这一点上,映射器对输入流中的元素进行必要的转换。这些元素中的每一个都可能被转换为多个项目,然后用来创建一个新的流。在我们的代码示例中,表达式Flux.just(s.toUpperCase().split(""))的值表示这样一个流。

Once a new stream – represented by a Publisher instance – is ready, flatMap eagerly subscribes. The operator doesn’t wait for the publisher to finish before moving on to the next stream, meaning the subscription is non-blocking.

一旦一个新的流(由Publisher实例代表)准备就绪,flatMap就会急切地订阅。操作者在转到下一个流之前不会等待发布者的完成,这意味着订阅是无阻塞的。

Since the pipeline handles all the derived streams simultaneously, their items may come in at any moment. As a result, the original order is lost. If the order of items is important, consider using the flatMapSequential operator instead.

由于管道同时处理所有的派生流,他们的项目可能在任何时候进来。因此,原来的顺序会丢失。如果项目的顺序很重要,可以考虑使用flatMapSequential操作符代替。

5. Differences Between map and flatMap

5.mapflatMap之间的区别

So far, we’ve covered the map and flatMap operators. Let’s wrap up with major differences between them.

到目前为止,我们已经介绍了mapflatMap操作符。让我们来总结一下它们之间的主要区别。

5.1. One-to-One vs. One-to-Many

5.1.一对一 VS 一对多

The map operator applies a one-to-one transformation to stream elements, while flatMap does one-to-many. This distinction is clear when looking at the method signature:

map操作符对流元素进行一对一的转换,而flatMap进行一对多的转换。这个区别在看方法签名时就很清楚了。

  • <V> Flux<V> map(Function<? super T, ? extends V> mapper) – the mapper converts a single value of type T to a single value of type V
  • Flux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper) – the mapper converts a single value of type T to a Publisher of elements of type R

We can see that in terms of functionality, the difference between map and flatMap in Project Reactor is similar to the difference between map and flatMap in the Java Stream API.

我们可以看到,就功能而言,Project Reactor中mapflatMap的区别类似于Java Stream API中mapflatMap的区别。

5.2. Synchronous vs. Asynchronous

5.2.同步与异步

Here are two extracts from the API specification for the Reactor Core library:

下面是Reactor核心库的API规范中的两段摘录。

  • map: Transform the items emitted by this Flux by applying a synchronous function to each item
  • flatMap: Transform the elements emitted by this Flux asynchronously into Publishers

It’s easy to see map is a synchronous operator – it’s simply a method that converts one value to another. This method executes in the same thread as the caller.

很容易看出map是一个同步操作符–它只是一个将一个值转换为另一个的方法。这个方法与调用者在同一个线程中执行。

The other statement – flatMap is asynchronous – is not that clear. In fact, the transformation of elements into Publishers can be either synchronous or asynchronous.

另一种说法–flatMap是异步的–并不那么明确。事实上,元素向Publishers的转化可以是同步的,也可以是异步的。

In our sample code, that operation is synchronous since we emit elements with the Flux#just method. However, when dealing with a source that introduces high latency, such as a remote server, asynchronous processing is a better option.

在我们的示例代码中,该操作是同步的,因为我们用Flux#just方法发射了元素。然而,当处理一个引入高延迟的源时,如远程服务器,异步处理是一个更好的选择

The important point is that the pipeline doesn’t care which threads the elements come from – it just pays attention to the publishers themselves.

重要的一点是,管道并不关心这些元素来自哪个线程–它只关注发布者本身。

6. Conclusion

6.结语

In this article, we’ve walked through the map and flatMap operators in Project Reactor. We discussed a couple of examples and clarified the process.

在这篇文章中,我们已经走过了Project Reactor中的mapflatMap操作符。我们讨论了几个例子,并阐明了这个过程。

As usual, the source code for our application is available over on GitHub.

像往常一样,我们的应用程序的源代码可在GitHub上获得