會(huì)用flatMap?瞧高手如何用它簡(jiǎn)化代碼!
環(huán)境:Java21
1. 簡(jiǎn)介
flatMap是函數(shù)式編程中的強(qiáng)大工具,它突破了傳統(tǒng)迭代與嵌套循環(huán)的思維局限。通過(guò)“先映射后扁平化”的操作,它能優(yōu)雅處理嵌套數(shù)據(jù)、集合轉(zhuǎn)換及異步流等復(fù)雜場(chǎng)景。無(wú)論是從多層結(jié)構(gòu)中提取信息、合并多個(gè)集合,還是在響應(yīng)式編程中展開(kāi)并整合數(shù)據(jù)流,flatMap都能以簡(jiǎn)潔的鏈?zhǔn)秸{(diào)用實(shí)現(xiàn)。掌握它,意味著代碼將告別冗余的循環(huán)與條件判斷,轉(zhuǎn)向更清晰、更具表達(dá)力的轉(zhuǎn)換邏輯。
本篇文章將詳細(xì)的介紹flatMap函數(shù)的各種場(chǎng)景下的使用。
2.實(shí)戰(zhàn)案例
2.1 flatMap基礎(chǔ)用法
假設(shè)你有一個(gè)List<List<String>>,而你想要得到一個(gè)包含所有字符串的單一列表。大多數(shù)開(kāi)發(fā)者會(huì)在這里使用flatMap,如下示例:
List<List<String>> names = List.of(
List.of("張三", "李四"),
List.of("曹查理", "大衛(wèi)"),
List.of("女?huà)z", "Jerry")
);
// 使用流(Stream)將嵌套的列表展平為一個(gè)單一的字符串列表
List<String> flattened = names.stream()
.flatMap(List::stream) // 將每個(gè)子列表中的元素“拉平”到一個(gè)流中
.toList(); // 收集結(jié)果為一個(gè)新列表
// 輸出展平后的結(jié)果
System.out.println(flattened);輸出結(jié)果
[張三, 李四, 曹查理, 大衛(wèi), 女?huà)z, Jerry]這是最基礎(chǔ)用法。我們沒(méi)有得到一個(gè)Stream<List<String>>,而是將其“扁平化”成了一個(gè)單一的Stream<String>。
2.2 使用Optional時(shí)的flatMap技巧
如果你正在使用一個(gè)可能存在也可能不存在的數(shù)據(jù)時(shí)。你可能會(huì)用 Optional 來(lái)處理這種情況。但是,如果你需要串聯(lián)多個(gè)操作呢?通常情況下,你會(huì)得到嵌套的Optional<Optional<T>>,這看起來(lái)就很頭大。而這正是flatMap大顯身手的地方。如下示例:
public static void main(String[] args) {
Optional<String> nameOpt = Optional.of("Spring Boot3實(shí)戰(zhàn)案例200講");
Optional<Optional<String>> result = nameOpt
.map(FlatMapDemo2::toUpperCase); // ? 得到嵌套的 Optional
System.out.println(result);
}
public static Optional<String> toUpperCase(String s) {
if (s == null || s.isEmpty()) {
return Optional.empty();
}
return Optional.of(s.toLowerCase());
}輸出結(jié)果
Optional[Optional[spring boot3實(shí)戰(zhàn)案例200講]]使用flatMap
Optional<String> nameOpt = Optional.of("Spring Boot3實(shí)戰(zhàn)案例200講");
Optional<String> result = nameOpt.flatMap(FlatMapDemo2::toUpperCase) ;
System.err.println(result) ;輸出結(jié)果
Optional[spring boot3實(shí)戰(zhàn)案例200講]總結(jié):
- map:會(huì)對(duì)結(jié)果進(jìn)行包裝,從而產(chǎn)生嵌套結(jié)構(gòu)。
- flatMap:會(huì)解開(kāi)包裝,保持簡(jiǎn)潔。
2.3 flatMap替代嵌套循環(huán)
嵌套循環(huán),會(huì)損害代碼的可讀性,尤其是在處理多維數(shù)據(jù)時(shí)。而flatMap可以用一種更優(yōu)雅的方式替代它們。
如下示例:兩個(gè)列表的笛卡爾積,假設(shè)你想要生成產(chǎn)品及其所有顏色組合的列表。
List<String> products = List.of("IPad", "IPhone", "Book");
List<String> colors = List.of("黑色", "白色");
List<String> result = products.stream()
.flatMap(product -> colors.stream()
.map(color -> product + " - " + color))
.collect(Collectors.toList());
result.forEach(System.out::println);輸出結(jié)果
IPad - 黑色
IPad - 白色
IPhone - 黑色
IPhone - 白色
Book - 黑色
Book - 白色如果不使用flatMap,你就得編寫(xiě)嵌套循環(huán)。而使用flatMap后,代碼會(huì)變得清晰、簡(jiǎn)潔且具有函數(shù)式風(fēng)格。
2.4 flatMap處理API響應(yīng)
在實(shí)際應(yīng)用中,你經(jīng)常需要從可能返回列表、可選字段或嵌套結(jié)構(gòu)的API中獲取數(shù)據(jù)。你可以使用flatMap直接轉(zhuǎn)換并扁平化結(jié)果,而無(wú)需進(jìn)行空值檢查。
如下示例:扁平化API數(shù)據(jù),假設(shè)有一個(gè)User類(lèi),其中包含一個(gè)返回電話(huà)號(hào)碼列表的方法:
public static void main(String[] args) {
List<User> users = List.of(
new User("Pack_xg", Arrays.asList("111", "222")),
new User("pack", Arrays.asList("333")),
new User("xg", Arrays.asList("444", "555"))
);
List<String> result = users.stream()
.flatMap(user -> user.phones().stream())
.toList() ;
System.out.println(result);
}
public static record User(String name, List<String> phones) {}輸出結(jié)果
[111, 222, 333, 444, 555]如果你用for循環(huán)實(shí)現(xiàn)此功能,代碼會(huì)顯得臃腫。而借助flatMap,只需一行代碼就能完成轉(zhuǎn)換。
2.5 flatMap + filter:秘密武器
大多數(shù)開(kāi)發(fā)者會(huì)使用filter和map,但很少將它們與flatMap結(jié)合使用。而正是這種結(jié)合能讓你解鎖另一層強(qiáng)大功能。如下示例:提取有效電子郵件地址
List<List<String>> emails = List.of(
List.of("pack_xg@gmail.com", "www.qq.com"),
List.of("pack@yahoo.com"),
List.of("xg@", "xxxooo@qq.com")
);
List<String> result = emails.stream()
.flatMap(List::stream)
.filter(email -> email.contains("@") && email.contains("."))
.toList() ;
System.out.println(result);輸出結(jié)果
[pack_xg@gmail.com, pack@yahoo.com, xxxooo@qq.com]我們無(wú)需逐個(gè)驗(yàn)證嵌套列表中的元素,而是可以在一個(gè)操作鏈中完成扁平化、過(guò)濾和收集。
2.6 flatMap與響應(yīng)式結(jié)合
在響應(yīng)式編程(如Reactor、RxJava)、函數(shù)式庫(kù)以及異步處理流程中你同樣可以使用flatMap。如下示例:響應(yīng)式流中的flatMap(Project Reactor)
首先,引入如下依賴(lài):
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.7.11</version>
</dependency>Flux使用flatMap
Flux<String> users = Flux.just("Pack_xg", "pack", "xg");
users.flatMap(user -> Flux.just(user.toUpperCase(), user.toLowerCase()))
.subscribe(System.out::println);輸出結(jié)果
PACK_XG
pack_xg
PACK
pack
XG
xg在這里,flatMap會(huì)處理每個(gè)用戶(hù),將其擴(kuò)展為兩個(gè)元素(大寫(xiě)形式 + 小寫(xiě)形式),然后將所有內(nèi)容合并為一個(gè)單一的流。
2.7 綜合示例
你需要獲取用戶(hù)數(shù)據(jù);每個(gè)用戶(hù)都有若干訂單;每個(gè)訂單都包含若干產(chǎn)品。而你想要得到一個(gè)扁平化的列表,其中包含所有用戶(hù)購(gòu)買(mǎi)的所有產(chǎn)品的名稱(chēng)。
準(zhǔn)備如下類(lèi):
//商品
public record Product(String name) {}
//訂單
public record Order(List<Product> products) {}
//用戶(hù)
public record User(String name, List<Order> orders) {}準(zhǔn)備數(shù)據(jù)
List<Product> p1 = List.of(
new Product("iPhone 15"),
new Product("AirPods Pro")
);
List<Product> p2 = List.of(
new Product("MacBook Pro")
);
List<Order> o1 = List.of(
new Order(p1),
new Order(p2)
);
User zhangsan = new User("張三", o1);
List<Product> lisiProducts1 = List.of(
new Product("iPad Air"),
new Product("Apple Watch")
);
List<Order> o2 = List.of(
new Order(lisiProducts1)
);
User lisi = new User("李四", o2);
User wangwu = new User("王五", List.of());
// 所有用戶(hù)
List<User> users = List.of(zhangsan, lisi, wangwu) ;傳統(tǒng)做法
List<String> productNames = new ArrayList<>();
for (User user : users) {
for (Order order : user.orders()) {
for (Product product : order.products()) {
productNames.add(product.name());
}
}
}
System.err.println(productNames) ;使用flatMap
List<String> productNames = users.stream()
.flatMap(user -> user.orders().stream())
.flatMap(order -> order.products().stream())
.map(Product::name)
.toList() ;總結(jié):掌握f(shuō)latMap不僅會(huì)讓你的代碼更簡(jiǎn)潔——它還會(huì)改變你的思維方式。你將不再編寫(xiě)那些千篇一律的循環(huán)代碼。



























