1. Overview
1.概述
In this tutorial, we’ll review the method Stream::mapMulti introduced in Java 16. We’ll write simple examples to illustrate how to use it. In particular, we’ll see that this method is similar to Stream::flatMap. We’ll cover under what circumstances we prefer to use mapMulti over flatMap.
在本教程中,我们将回顾Java 16中引入的方法Stream::mapMulti。我们将写一些简单的例子来说明如何使用它。特别是,我们将看到这个方法与Stream::flatMap相似。我们将介绍在什么情况下我们更愿意使用mapMulti而不是flatMap。
Be sure to check out our articles on Java Streams for a deeper dive into the Stream API.
请务必查看我们关于Java Streams的文章,以便更深入地了解Stream API。
2. Method Signature
2.方法签名
Omitting the wildcards, the mapMulti method can be written more succinctly:
省略通配符,mapMulti方法可以写得更简洁。
<R> Stream<R> mapMulti(BiConsumer<T, Consumer<R>> mapper)
It’s a Stream intermediate operation. It requires as a parameter the implementation of a BiConsumer functional interface. The implementation of the BiConsumer takes a Stream element T, if necessary, transforms it into type R, and invokes the mapper’s Consumer::accept.
它是一个Stream中间操作。它需要一个BiConsumer功能接口的实现作为参数。BiConsumer的实现需要一个Stream元素T,如果需要的话,将其转换为typeR,并调用mapper的Consumer::accept。
Inside Java’s mapMulti method implementation, the mapper is a buffer that implements the Consumer functional interface.
在Java的mapMulti方法实现里面,mapper是一个实现Consumer功能接口的缓冲器。
Each time we invoke Consumer::accept, it accumulates the elements in the buffer and passes them to the stream pipeline.
每次我们调用Consumer::accept,时,它都会积累缓冲区中的元素并将它们传递给流管道。
3. Simple Implementation Example
3.简单的实施例子
Let’s consider a list of integers to do the following operation:
让我们考虑用一个整数列表来做以下操作。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
double percentage = .01;
List<Double> evenDoubles = integers.stream()
.<Double>mapMulti((integer, consumer) -> {
if (integer % 2 == 0) {
consumer.accept((double) integer * ( 1 + percentage));
}
})
.collect(toList());
In our lambda implementation of BiConsumer<T, Consumer<R>> mapper, we first select only even integers, then we add to them the amount specified in percentage, cast the result into a double, and finish invoking consumer.accept.
在我们的BiConsumer<T, Consumer<R>> mapper的lambda实现中,我们首先只选择偶数整数,然后将percentage中指定的金额加入其中,将结果铸成double,并完成调用consumer.accept。
As we saw before, the consumer is just a buffer that passes the return elements to the stream pipeline. (As a side note, notice that we have to use a type witness <Double>mapMulti for the return value because otherwise, the compiler cannot infer the right type of R in the method’s signature.)
正如我们之前看到的,consumer只是一个缓冲区,它将返回元素传递给流管道。(作为附带说明,注意我们必须使用一个类型见证<Double>mapMulti来表示返回值,因为否则,编译器无法推断出方法签名中R的正确类型。)
This is either a one-to-zero or one-to-one transformation depending on whether the element is odd or even.
这是一个一对零或一对一的转换,这取决于元素是奇数还是偶数。
Notice that the if statement in the previous code sample plays the role of a Stream::filter, and casting the integer into a double, the role of a Stream::map. Hence, we could use Stream’s filter and map to achieve the same result:
请注意,前面的代码示例中的if 语句起着Stream::filter的作用,而将整数铸成双数,起着Stream::map的作用。因此,我们可以使用Stream的filter和map来实现同样的结果。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
double percentage = .01;
List<Double> evenDoubles = integers.stream()
.filter(integer -> integer % 2 == 0)
.<Double>map(integer -> ((double) integer * ( 1 + percentage)))
.collect(toList());
However, the mapMulti implementation is more direct since we don’t need to invoke so many stream intermediate operations.
然而,mapMulti实现更直接,因为我们不需要调用那么多流的中间操作。
Another advantage is that the mapMulti implementation is imperative, giving us more freedom to do element transformations.
另一个好处是,mapMulti的实现是必须的,让我们有更多的自由来做元素转换。
To support int, long, and double primitive types, we have mapMultiToDouble, mapMultiToInt, and mapMultiToLong variations of mapMulti.
为了支持int、long和double原始类型,我们有mapMultiToDouble、mapMultiToInt和mapMultiToLong的变化。
For example, we can use mapMultiToDouble to find the sum of the previous List of doubles:
例如,我们可以使用mapMultiToDouble来查找前面List的双数之和。
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5);
double percentage = .01;
double sum = integers.stream()
.mapMultiToDouble((integer, consumer) -> {
if (integer % 2 == 0) {
consumer.accept(integer * (1 + percentage));
}
})
.sum();
4. More Realistic Example
4.更现实的例子
Let’s consider a collection of Albums:
让我们考虑一个Albums的集合。
public class Album {
private String albumName;
private int albumCost;
private List<Artist> artists;
Album(String albumName, int albumCost, List<Artist> artists) {
this.albumName = albumName;
this.albumCost = albumCost;
this.artists = artists;
}
// ...
}
Each Album has a list of Artists:
每张专辑都有一个艺术家的列表。
public class Artist {
private final String name;
private boolean associatedMajorLabels;
private List<String> majorLabels;
Artist(String name, boolean associatedMajorLabels, List<String> majorLabels) {
this.name = name;
this.associatedMajorLabels = associatedMajorLabels;
this.majorLabels = majorLabels;
}
// ...
}
If we want to collect a list of artist-album name pairs, we can implement it using mapMulti:
如果我们想收集一个艺术家-专辑名称对的列表,我们可以用mapMulti来实现它。
List<Pair<String, String>> artistAlbum = albums.stream()
.<Pair<String, String>> mapMulti((album, consumer) -> {
for (Artist artist : album.getArtists()) {
consumer.accept(new ImmutablePair<String, String>(artist.getName(), album.getAlbumName()));
}
})
For each album in the stream, we iterate over the artists, create an Apache Commons ImmutablePair of artist-album names, and invoke Consumer::accept. The implementation of mapMulti accumulates the elements accepted by the consumer and passes them to the stream pipeline.
对于流中的每张专辑,我们遍历艺术家,创建一个艺术家-专辑名称的Apache Commons ImmutablePair,并调用Consumer::accept。mapMulti的实现积累了消费者所接受的元素,并将它们传递给流管道。
This has the effect of a one-to-many transformation where the results are accumulated in the consumer but ultimately are flattened into a new stream. This is essentially what Stream::flatMap does so that we can achieve the same result with the following implementation:
这具有一对多转换的效果,结果在消费者中被累积,但最终被平铺到一个新的流中。这基本上就是Stream::flatMap所做的,所以我们可以通过下面的实现达到同样的效果。
List<Pair<String, String>> artistAlbum = albums.stream()
.flatMap(album -> album.getArtists()
.stream()
.map(artist -> new ImmutablePair<String, String>(artist.getName(), album.getAlbumName())))
.collect(toList());
We see that both methods give identical results. We’ll cover next in which cases it is more advantageous to use mapMulti.
我们看到,这两种方法的结果是相同的。接下来我们将介绍在哪些情况下使用mapMulti更有利。
5. When to Use mapMulti Instead of flatMap
5.何时使用mapMulti而不是flatMap?
5.1. Replacing Stream Elements with a Small Number of Elements
5.1.用少量元素替换流元素
As stated in the Java documentation: “when replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating a new Stream instance for every group of result elements, as required by flatMap”.
正如Java文档中所说。”当用少量(可能是零)的元素替换每个流元素时。使用这种方法可以避免为每一组结果元素创建一个新的Stream实例的开销,正如flatMap所要求的那样”。
Let’s write a simple example that illustrates this scenario:
让我们写一个简单的例子来说明这种情况。
int upperCost = 9;
List<Pair<String, String>> artistAlbum = albums.stream()
.<Pair<String, String>> mapMulti((album, consumer) -> {
if (album.getAlbumCost() < upperCost) {
for (Artist artist : album.getArtists()) {
consumer.accept(new ImmutablePair<String, String>(artist.getName(), album.getAlbumName()));
}
}
})
For each album, we iterate over the artists and accumulate zero or few artist-album pairs, depending on the album’s price compared with the variable upperCost.
对于每张专辑,我们遍历艺术家,并根据专辑的价格与变量upperCost相比,积累零或少数艺术家-专辑对。
To accomplish the same results using flatMap:
要使用flatMap完成同样的结果。
int upperCost = 9;
List<Pair<String, String>> artistAlbum = albums.stream()
.flatMap(album -> album.getArtists()
.stream()
.filter(artist -> upperCost > album.getAlbumCost())
.map(artist -> new ImmutablePair<String, String>(artist.getName(), album.getAlbumName())))
.collect(toList());
We see that the imperative implementation of mapMulti is more performant — we don’t have to create intermediate streams with each processed element as we do with the declarative approach of flatMap.
我们看到mapMulti的命令式实现更具性能–我们不必像flatMap的声明式方法那样为每个处理的元素创建中间流。
5.2. When It’s Easier to Generate Result Elements
5.2.当生成结果元素更容易的时候
Let’s write in the Album class a method that passes all the artist-album pairs with their associated major labels to a consumer:
让我们在Album类中写一个方法,将所有艺术家-专辑对及其相关的主要标签传递给消费者。
public class Album {
//...
public void artistAlbumPairsToMajorLabels(Consumer<Pair<String, String>> consumer) {
for (Artist artist : artists) {
if (artist.isAssociatedMajorLabels()) {
String concatLabels = artist.getMajorLabels().stream().collect(Collectors.joining(","));
consumer.accept(new ImmutablePair<>(artist.getName()+ ":" + albumName, concatLabels));
}
}
}
// ...
}
If the artist has an association with major labels, the implementation joins the labels into a comma-separated string. It then creates a pair of artist-album names with the labels and invokes Consumer::accept.
如果艺术家与主要标签有关联,该实现将这些标签连接成一个逗号分隔的字符串。然后它创建一对带有标签的艺术家-专辑名称,并调用Consumer::accept。
If we want to get a list of all the pairs, it’s as simple as using mapMulti with the method reference Album::artistAlbumPairsToMajorLabels:
如果我们想得到所有配对的列表,就像使用mapMulti和方法参考Album::artistAlbumPairsToMajorLabels一样简单。
List<Pair<String, String>> copyrightedArtistAlbum = albums.stream()
.<Pair<String, String>> mapMulti(Album::artistAlbumPairsToMajorLabels)
.collect(toList());
We see that, in more complex cases, we could have very sophisticated implementations of the method reference. For instance, the Java documentation gives an example using recursion.
我们看到,在更复杂的情况下,我们可以对方法引用进行非常复杂的实现。例如,Java文档给出了一个使用递归的例子。
In general, replicating the same results using flatMap will be very difficult. Therefore, we should use mapMulti in cases where generating result elements is much easier than returning them in the form of a Stream as required in flatMap.
一般来说,使用flatMap来复制同样的结果将非常困难。因此,我们应该在生成结果元素比以Stream的形式返回要容易得多的情况下使用mapMulti,正如flatMap所要求的那样。
6. Conclusion
6.结论
In this tutorial, we’ve covered how to implement mapMulti with different examples. We’ve seen how it compares with flatMap and when it’s more advantageous to use.
在本教程中,我们通过不同的例子介绍了如何实现mapMulti。我们看到了它与flatMap的比较,以及何时使用它更有利。
In particular, it’s recommended to use mapMulti when a few stream elements need to be replaced or when it’s easier to use an imperative approach to generate the elements of the stream pipeline.
特别是,当少数流元素需要被替换时,或者当使用命令式方法来生成流管道的元素更容易时,建议使用mapMulti。
The source code can be found over on GitHub.
源代码可以在GitHub上找到over。