Stream API中的mapMulti指南
- Stream
- java
1. 概述
在本教程中,我們將回顧Stream::mapMulti
方法。我們將編寫簡單的示例來說明如何使用它。特別是,我們會看到這個方法類似於Stream::
flatMap
。我們將介紹在什麼情況下我們更喜歡使用mapMulti
不是flatMap
。
請務必查看我們關於Java Streams
的文章,以更深入地了解 Stream API。
2. 方法簽名
省略通配符, mapMulti
方法可以寫得更簡潔:
<R> Stream<R> mapMulti(BiConsumer<T, Consumer<R>> mapper)
這是一個Stream
中間操作。 BiConsumer
功能接口的實現作為參數。 BiConsumer
的實現採用Stream
元素T
,如有必要,將其轉換為type
R
,並調用mapper'
的Consumer::accept
。
在 Java 的mapMulti
方法實現中, mapper
是一個緩衝區,它實現了Consumer
功能接口。
每次我們調用Consumer::accept,
它都會累積緩衝區中的元素並將它們傳遞給流管道。
3. 簡單實現示例
讓我們考慮一個整數列表來執行以下操作:
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());
BiConsumer<T, Consumer<R>> mapper
的 lambda 實現中,我們首先只選擇偶數整數,然後我們將指定的數量添加到percentage
,將結果轉換為double,
精度數,並完成調用consumer.accept
。
正如我們之前看到的, consumer
只是一個緩衝區,它將返回元素傳遞給流管道。 (作為旁注,請注意我們必須使用類型見證<Double>mapMulti
作為返回值,否則編譯器無法在方法簽名中R
根據元素是奇數還是偶數,這是一對零或一對一轉換。
請注意,前面代碼示例中if statement
Stream::filter
的角色,並將整數轉換為 double,扮演Stream::map
的角色。因此,我們可以使用Stream's
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());
然而, mapMulti
實現更直接,因為我們不需要調用這麼多的流中間操作。
另一個優點是**mapMulti
實現是必要的,讓我們可以更自由地進行元素轉換**。
為了支持int
, long
和double
原始類型,我們有mapMultiToDouble
, mapMultiToInt,
並mapMultiToLong
的變化mapMulti
。
例如,我們可以使用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. 更現實的例子
讓我們考慮一個Album
的集合:
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;
}
// ...
}
每個Album
都有一個Artist
列表:
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;
}
// ...
}
如果我們想收集藝術家-專輯名稱對的列表,我們可以使用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()));
}
})
對於流中的每個專輯,我們遍歷藝術家,創建ImmutablePair
,並調用 C onsumer::accept
。 mapMulti
的實現累積消費者接受的元素並將它們傳遞給流管道。
這具有一對多轉換的效果,其中結果在消費者中累積,但最終被扁平化為新的流。這本質上是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());
我們看到這兩種方法給出了相同的結果。接下來我們將介紹在哪些情況下使用mapMulti
更有利。
5. 何時使用mapMulti
而不是flatMap
5.1.用少量元素替換流元素
正如 Java 文檔中所述:“當用少量(可能為零)元素替換每個流元素時。使用這種方法可以避免為每組結果元素Stream
flatMap”.
讓我們寫一個簡單的例子來說明這個場景:
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()));
}
}
})
對於每張專輯,我們迭代藝術家並積累零個或幾個藝術家-專輯對,這取決於專輯的價格與變量upperCost
相比。
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());
我們看到**mapMulti
的命令式實現的flatMap
的聲明式方法那樣為每個處理過的元素創建中間流**。
5.2.何時更容易生成結果元素
讓我們在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));
}
}
}
// ...
}
如果藝術家與主要標籤有關聯,則實現會將標籤連接到逗號分隔的字符串中。然後它創建一對帶有標籤的藝術家專輯名稱並調用 C onsumer::accept
。
如果我們想獲得所有對的列表,就像使用mapMulti
和方法引用Album::artistAlbumPairsToMajorLabels
一樣簡單:
List<Pair<String, String>> copyrightedArtistAlbum = albums.stream()
.<Pair<String, String>> mapMulti(Album::artistAlbumPairsToMajorLabels)
.collect(toList());
我們看到,在更複雜的情況下,我們可以有非常複雜的方法引用實現。例如, Java 文檔給出了一個使用遞歸的示例。
latMap
複製相同的結果會非常困難。 **flatMap .**
要求以Stream
的形式返回它們更容易的情況下,我們應該使用mapMulti
**flatMap** .