使用Java Stream將集合轉(zhuǎn)換為一對(duì)一Map的方法詳解
引言
在日常的開(kāi)發(fā)工作中,我們經(jīng)常使用到Java Stream,特別是Stream API中提供的Collectors.toList()收集器,但有些場(chǎng)景下,我們需要將集合轉(zhuǎn)換為Map,這時(shí)候就需要使用到Stream API中提供的另一個(gè)收集器:Collectors.toMap,它可以將流中的元素映射為鍵值對(duì),并收集到一個(gè)Map中。
1. 三種主要的重載方法
Collectors.toMap有3種重載方法,分別是:
1)兩個(gè)參數(shù)的重載方法(最簡(jiǎn)單的形式)
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
2)三個(gè)參數(shù)的重載方法(包含沖突處理)
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
3)四個(gè)參數(shù)的重載方法(指定Map實(shí)現(xiàn))
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}
接下來(lái),我們結(jié)合使用示例詳細(xì)講解。
2. 使用示例
2.1 將對(duì)象的某些屬性轉(zhuǎn)換為Map
假設(shè)有一個(gè)城市列表,需要將其轉(zhuǎn)換為Map,其中Key為城市ID、Value為城市名稱,轉(zhuǎn)換方法如下所示:
@Getter
@Setter
public class City {
private Integer cityId;
private String cityName;
public City(Integer cityId, String cityName) {
this.cityId = cityId;
this.cityName = cityName;
}
}
List<City> cityList = Arrays.asList(
new City(1, "北京"),
new City(2, "上海"),
new City(3, "廣州"),
new City(4, "深圳")
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);
輸出結(jié)果:
{1=北京, 2=上海, 3=廣州, 4=深圳}
2.2 將對(duì)象列表轉(zhuǎn)換為Map(ID -> 對(duì)象)
仍然使用上面的城市列表,需要將其轉(zhuǎn)換為Map,其中Key為城市ID、Value為城市對(duì)象,轉(zhuǎn)換方法如下所示:
List<City> cityList = Arrays.asList(
new City(1, "北京"),
new City(2, "上海"),
new City(3, "廣州"),
new City(4, "深圳")
);
Map<Integer, City> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, city -> city));
City city = cityMap.get(1);
System.out.println("城市ID: " + city.getCityId());
System.out.println("城市名稱: " + city.getCityName());
輸出結(jié)果如下所示:
城市ID: 1 城市名稱: 北京
上面的寫法等價(jià)于:
Map<Integer, City> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, Function.identity()));
因?yàn)?code>Function.identity()內(nèi)部實(shí)現(xiàn)是下面這樣的:
static <T> Function<T, T> identity() {
return t -> t;
}
2.3 鍵沖突處理
假設(shè)上面的城市列表中有一個(gè)ID重復(fù)的城市:
List<City> cityList = Arrays.asList(
new City(1, "北京"),
new City(2, "上海"),
new City(3, "廣州"),
new City(4, "深圳"),
new City(4, "天津")
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println("城市ID: 4, 城市名稱: " + cityMap.get(4));
此時(shí)運(yùn)行代碼,會(huì)拋出java.lang.IllegalStateException異常,如下圖所示:

有3種常見(jiàn)的鍵沖突處理方式,分別是保留舊值、使用新值和合并值,接下來(lái)一一講解。
1)方式一:保留舊值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> oldValue));
輸出結(jié)果:
城市ID: 4, 城市名稱: 深圳
2)方式二:使用新值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> newValue));
輸出結(jié)果:
城市ID: 4, 城市名稱: 天津
3)方式三:合并值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName,
(oldValue, newValue) -> oldValue + ", " + newValue));
輸出結(jié)果:
城市ID: 4, 城市名稱: 深圳, 天津
2.4 數(shù)據(jù)分組聚合
假設(shè)有一個(gè)銷售記錄列表,需要將其轉(zhuǎn)換為Map,其中Key為銷售員、Value為該銷售員的總銷售額,轉(zhuǎn)換方法如下所示:
@Getter
@Setter
public class SalesRecord {
private String salesPerson;
private BigDecimal amount;
public SalesRecord(String salesPerson, BigDecimal amount) {
this.salesPerson = salesPerson;
this.amount = amount;
}
}
List<SalesRecord> salesRecordList = Arrays.asList(
new SalesRecord("張三", new BigDecimal("1000")),
new SalesRecord("李四", new BigDecimal("2000")),
new SalesRecord("張三", new BigDecimal("980"))
);
Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
.collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::add));
System.out.println(salesRecordMap);
輸出結(jié)果:
{李四=2000, 張三=1980}
上面的例子是銷售額累加,也可以只取最小值:
Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
.collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::min));
此時(shí)的輸出結(jié)果:
{李四=2000, 張三=980}
或者只取最大值:
Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
.collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::max));
此時(shí)的輸出結(jié)果:
{李四=2000, 張三=1000}
2.5 指定Map實(shí)現(xiàn)
默認(rèn)情況下,Collectors.toMap是將結(jié)果收集到HashMap中,如果有需要,我們也可以指定成TreeMap或者LinkedHashMap。
如果想要保持插入順序,可以指定使用LinkedHashMap:
List<City> cityList = Arrays.asList(
new City(2, "上海"),
new City(1, "北京"),
new City(4, "深圳"),
new City(3, "廣州")
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName,
(existing, replacement) -> existing, LinkedHashMap::new));
System.out.println(cityMap);
輸出結(jié)果:
{2=上海, 1=北京, 4=深圳, 3=廣州}
如果想要按鍵排序,可以指定使用TreeMap:
List<City> cityList = Arrays.asList(
new City(2, "上海"),
new City(1, "北京"),
new City(4, "深圳"),
new City(3, "廣州")
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName,
(existing, replacement) -> existing, TreeMap::new));
System.out.println(cityMap);
輸出結(jié)果:
{1=北京, 2=上海, 3=廣州, 4=深圳}
3. 注意事項(xiàng)
3.1 空異常
如果valueMapper中取出的值有null值,會(huì)拋出java.lang.NullPointerException異常,如下示例:
List<City> cityList = Arrays.asList(
new City(1, "北京"),
new City(2, "上海"),
new City(3, "廣州"),
new City(4, "深圳"),
new City(5, null)
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);
運(yùn)行以上代碼會(huì)拋出異常,如下圖所示:

有兩種解決方案,第一種解決方案是過(guò)濾null值:
Map<Integer, String> cityMap = cityList.stream()
.filter(city -> city.getCityName() != null)
.collect(Collectors.toMap(City::getCityId, City::getCityName));
第二種解決方案是提供默認(rèn)值:
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId,
city -> Optional.ofNullable(city.getCityName()).orElse("未知")));
3.2 鍵重復(fù)異常
如果出現(xiàn)重復(fù)鍵,且沒(méi)有提供mergeFunction參數(shù),會(huì)拋出java.lang.IllegalStateException異常,如下示例:
List<City> cityList = Arrays.asList(
new City(1, "北京"),
new City(2, "上海"),
new City(3, "廣州"),
new City(4, "深圳"),
new City(4, "天津")
);
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName));
System.out.println(cityMap);
運(yùn)行以上代碼會(huì)拋出異常,如下圖所示:

解決方案見(jiàn)本篇文章2.3 鍵沖突處理部分。
4. 總結(jié)
Collectors.toMap是Stream API中提供的一個(gè)非常方便的收集器,它可以將流中的元素映射為鍵值對(duì),并收集到一個(gè)Map中。
它適用于一對(duì)一映射的場(chǎng)景,但在使用時(shí),要注意避免java.lang.NullPointerException異常和
java.lang.IllegalStateException異常。
以上就是使用Java Stream將集合轉(zhuǎn)換為一對(duì)一Map的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Java Stream集合轉(zhuǎn)一對(duì)一Map的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringMVC獲取HTTP中元素的實(shí)現(xiàn)示例
本文主要介紹了SpringMVC獲取HTTP中的元素,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
ReentrantrantLock底層實(shí)現(xiàn)原理及分析
本文詳細(xì)介紹了Java并發(fā)包中ReentrantLock的實(shí)現(xiàn)原理,包括非公平鎖和公平鎖的加鎖和解鎖機(jī)制,以及可重入和可打斷特性,同時(shí),還介紹了條件變量的實(shí)現(xiàn)原理,通過(guò)await和signal方法來(lái)管理線程間的等待和喚醒2026-03-03
Spring Security自定義認(rèn)證器的實(shí)現(xiàn)代碼
這篇文章主要介紹了Spring Security自定義認(rèn)證器的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
Spring?Boot?Admin?添加報(bào)警提醒和登錄驗(yàn)證功能的具體實(shí)現(xiàn)
報(bào)警提醒功能是基于郵箱實(shí)現(xiàn)的,當(dāng)然也可以使用其他的提醒功能,如釘釘或飛書機(jī)器人提醒也是可以的,但郵箱報(bào)警功能的實(shí)現(xiàn)成本最低,所以本文我們就來(lái)看郵箱的報(bào)警提醒功能的具體實(shí)現(xiàn)2022-01-01
SpringMVC使用@PathVariable接收參數(shù)過(guò)程解析
這篇文章主要介紹了SpringMVC使用@PathVariable接收參數(shù)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
Java聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之實(shí)現(xiàn)聊天室服務(wù)端功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下2022-10-10
SpringBoot集成SwaggerUi以及啟動(dòng)時(shí)遇到的錯(cuò)誤
這篇文章主要介紹了SpringBoot集成SwaggerUi以及啟動(dòng)時(shí)遇到的錯(cuò)誤,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06

