使用Java Stream,将集合转换为一对一Map
2025-12-26 11:23 申城异乡人 阅读(101) 评论(0) 收藏 举报在日常的开发工作中,我们经常使用到Java Stream,特别是Stream API中提供的Collectors.toList()收集器,
但有些场景下,我们需要将集合转换为Map,这时候就需要使用到Stream API中提供的另一个收集器:
Collectors.toMap,它可以将流中的元素映射为键值对,并收集到一个Map中。
1. 三种主要的重载方法
Collectors.toMap有3种重载方法,分别是:
1)两个参数的重载方法(最简单的形式)
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)三个参数的重载方法(包含冲突处理)
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)四个参数的重载方法(指定Map实现)
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);
}
接下来,我们结合使用示例详细讲解。
2. 使用示例
2.1 将对象的某些属性转换为Map
假设有一个城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市名称,转换方法如下所示:
@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);
输出结果:
2.2 将对象列表转换为Map(ID -> 对象)
仍然使用上面的城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市对象,转换方法如下所示:
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());
输出结果如下所示:
城市ID: 1
城市名称: 北京
上面的写法等价于:
Map<Integer, City> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, Function.identity()));
因为Function.identity()内部实现是下面这样的:
static <T> Function<T, T> identity() {
return t -> t;
}
2.3 键冲突处理
假设上面的城市列表中有一个ID重复的城市:
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));
此时运行代码,会抛出java.lang.IllegalStateException异常,如下图所示:

有3种常见的键冲突处理方式,分别是保留旧值、使用新值和合并值,接下来一一讲解。
1)方式一:保留旧值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> oldValue));
输出结果:
城市ID: 4, 城市名称: 深圳
2)方式二:使用新值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> newValue));
输出结果:
城市ID: 4, 城市名称: 天津
3)方式三:合并值
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId, City::getCityName,
(oldValue, newValue) -> oldValue + ", " + newValue));
输出结果:
城市ID: 4, 城市名称: 深圳, 天津
2.4 数据分组聚合
假设有一个销售记录列表,需要将其转换为Map,其中Key为销售员、Value为该销售员的总销售额,转换方法如下所示:
@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);
输出结果:
上面的例子是销售额累加,也可以只取最小值:
Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
.collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::min));
此时的输出结果:
或者只取最大值:
Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
.collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::max));
此时的输出结果:
2.5 指定Map实现
默认情况下,Collectors.toMap是将结果收集到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);
输出结果:
如果想要按键排序,可以指定使用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);
输出结果:
3. 注意事项
3.1 空异常
如果valueMapper中取出的值有null值,会抛出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);
运行以上代码会抛出异常,如下图所示:

有两种解决方案,第一种解决方案是过滤null值:
Map<Integer, String> cityMap = cityList.stream()
.filter(city -> city.getCityName() != null)
.collect(Collectors.toMap(City::getCityId, City::getCityName));
第二种解决方案是提供默认值:
Map<Integer, String> cityMap = cityList.stream()
.collect(Collectors.toMap(City::getCityId,
city -> Optional.ofNullable(city.getCityName()).orElse("未知")));
3.2 键重复异常
如果出现重复键,且没有提供mergeFunction参数,会抛出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);
运行以上代码会抛出异常,如下图所示:

解决方案见本篇文章2.3 键冲突处理部分。
4. 总结
Collectors.toMap是Stream API中提供的一个非常方便的收集器,它可以将流中的元素映射为键值对,并收集到一个Map中。
它适用于一对一映射的场景,但在使用时,要注意避免java.lang.NullPointerException异常和
java.lang.IllegalStateException异常。
文章持续更新,欢迎关注微信公众号「申城异乡人」第一时间阅读!
浙公网安备 33010602011771号