使用Java Stream将集合转换为一对一Map的方法详解

 更新时间:2025年12月29日 08:42:36   作者:申城异乡人  
在日常的开发工作中,我们经常使用到Java Stream,特别是Stream API中提供的Collectors.toList()收集器,但有些场景下,我们需要将集合转换为Map,所以本文就给大家介绍了如何使用Java Stream将集合转换为一对一Map,需要的朋友可以参考下

引言

在日常的开发工作中,我们经常使用到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);

输出结果:

{1=北京, 2=上海, 3=广州, 4=深圳}

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);

输出结果:

{李四=2000, 张三=1980}

上面的例子是销售额累加,也可以只取最小值:

Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
        .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::min));

此时的输出结果:

{李四=2000, 张三=980}

或者只取最大值:

Map<String, BigDecimal> salesRecordMap = salesRecordList.stream()
        .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::max));

此时的输出结果:

{李四=2000, 张三=1000}

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);

输出结果:

{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);

输出结果:

{1=北京, 2=上海, 3=广州, 4=深圳}

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异常。

以上就是使用Java Stream将集合转换为一对一Map的方法详解的详细内容,更多关于Java Stream集合转一对一Map的资料请关注脚本之家其它相关文章!

相关文章

  • SpringMVC获取HTTP中元素的实现示例

    SpringMVC获取HTTP中元素的实现示例

    本文主要介绍了SpringMVC获取HTTP中的元素,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • ReentrantrantLock底层实现原理及分析

    ReentrantrantLock底层实现原理及分析

    本文详细介绍了Java并发包中ReentrantLock的实现原理,包括非公平锁和公平锁的加锁和解锁机制,以及可重入和可打断特性,同时,还介绍了条件变量的实现原理,通过await和signal方法来管理线程间的等待和唤醒
    2026-03-03
  • Spring Security自定义认证器的实现代码

    Spring Security自定义认证器的实现代码

    这篇文章主要介绍了Spring Security自定义认证器的实现代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • Spring Boot Admin 添加报警提醒和登录验证功能的具体实现

    Spring Boot Admin 添加报警提醒和登录验证功能的具体实现

    报警提醒功能是基于邮箱实现的,当然也可以使用其他的提醒功能,如钉钉或飞书机器人提醒也是可以的,但邮箱报警功能的实现成本最低,所以本文我们就来看邮箱的报警提醒功能的具体实现
    2022-01-01
  • SpringMVC使用@PathVariable接收参数过程解析

    SpringMVC使用@PathVariable接收参数过程解析

    这篇文章主要介绍了SpringMVC使用@PathVariable接收参数过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java实现图片模糊效果详解

    Java实现图片模糊效果详解

    图片模糊是图像处理中的一种常见效果,它通过平均周围像素的颜色来使图像变得模糊,下面我们来看看如何使用Swing库实现图片模糊效果吧
    2025-02-02
  • Java聊天室之实现聊天室服务端功能

    Java聊天室之实现聊天室服务端功能

    这篇文章主要为大家详细介绍了Java简易聊天室之实现聊天室服务端功能,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以了解一下
    2022-10-10
  • SpringBoot集成SwaggerUi以及启动时遇到的错误

    SpringBoot集成SwaggerUi以及启动时遇到的错误

    这篇文章主要介绍了SpringBoot集成SwaggerUi以及启动时遇到的错误,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • java中Collections.sort排序详解

    java中Collections.sort排序详解

    这篇文章主要介绍了java中Collections.sort排序详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • java jvm的知识详细介绍

    java jvm的知识详细介绍

    这篇文章主要介绍了java jvm的知识详细介绍的相关资料,这里对java jvm中的堆内存和栈内存等基础知识做了详细介绍,需要的朋友可以参考下
    2016-11-11

最新评论