Java8 Collectors.toMap() 的使用

本文章已经生成可运行项目,

一、简单介绍

Collectors.toMap(): JDK8 中提供,用于将 Stream 流转换为 Map。

用法1:根据某一属性,对对象的实例或属性做映射

例如:使用 Stream 想要将集合的某一属性(例如手机号)作为 key,对象本身作为 value,这样我们在根据属性获取实例或实例的其他属性时就可以省去遍历每个对象的时间。

// 获取 手机号-UserInfo 映射
Map<String, UserInfo> phoneNumberMap = list.stream().collect(Collectors.toMap(UserInfo::getPhoneNumber(), Function.identity());

用法2:根据某一属性,对对象集合进行去重

原始 JDK8 实现根据某一属性去重:

// 查询数据
List<UserInfo> list = userInfoMapper.getList();
// 根据 姓名 去重
list = list.stream().filter(o -> o.getName() != null).collect(
                Collectors.collectingAndThen(Collectors.toCollection(
                    () -> new TreeSet<>(Comparator.comparing(UserInfo::getName))), ArrayList<Aoo>::new));

通过 Collectors.toMap() 实现根据某一属性去重:

// 查询数据
List<UserInfo> list = userInfoMapper.getList();
// 根据 姓名 去重
Map<String, UserInfo> collect = list.stream()
    .collect(Collectors.toMap(UserInfo::getName, o -> o, (v1, v2) -> v1));
list = new ArrayList<>(collect.values());

二、Duplicate key 异常

1)异常重现:

    public static void main(String[] args) {
        List<User> list = Arrays.asList(
                new User("张三", 15),
                new User("张三", 16));
        // 获取 姓名-年龄 映射
        Map<String, Integer> nameMap = list.stream().collect(Collectors.toMap(User::getName, User::getAge));
        System.out.println(nameMap);
    }

    private static class User {
        private String name;
        private Integer age;
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public Integer getAge() {
            return age;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

2)异常截图:

在这里插入图片描述

3)异常说明:

​ Collectors.toMap() 在 key 重复的时候,是需要指定处理操作的。默认并不会像 HashMap 一样直接对原值进行覆盖。当出现 key 重复但没有指定操作时,就会抛出一个 IllegalStateException 非法声明异常。

4)异常处理:

​ 我们只需要增加第3个参数,指定当 key 重复时需要进行的操作即可:

// 获取 姓名-年龄 映射
// 第3个参数会执行 Map.merge() 操作,(v1, v2) -> v1 表示重复时抛弃后面的值
Map<String, Integer> nameMap = list.stream()
    .collect(Collectors.toMap(User::getName, User::getAge, (v1, v2) -> v1));

​ 运行程序,输出结果如下:

三、Collectors.toMap() 导致的空指针异常

1)异常重现:

    public static void main(String[] args) {
        List<User> list = Arrays.asList(
                new User("张三", 15),
                new User("李四", null));
        // 获取 姓名-年龄 映射
        Map<String, Integer> nameMap = list.stream()
            .collect(Collectors.toMap(User::getName, User::getAge, (v1, v2) -> v1));
        System.out.println(nameMap);
    }

    private static class User {
        private String name;
        private Integer age;
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public Integer getAge() {
            return age;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }

2)异常截图:

在这里插入图片描述

3)异常说明:

​ 导致空指针是因为 Collectors.toMap() 底层会调用 HashMap.merge() 方法,在执行 merge() 方法的时候会对 value 进行非空判断,从而抛出异常:

在这里插入图片描述

4)异常处理:

方法一:替换 null 为一个默认值,比如 -1

// 获取 姓名-年龄 映射
Map<String, Integer> nameMap = list.stream()
    .collect(Collectors.toMap(User::getName, o -> Optional.ofNullable(o.getAge()).orElse(-1), (v1, v2) -> v1));

运行程序,输出结果如下:

方法二:调用 collect() 方法实现

public static void main(String[] args) {
    List<User> list = Arrays.asList(
        new User("张三", 15),
        new User("张三", 16),
        new User("李四", null));
    Map<String, Integer> nameMap = list.stream()
        .collect(HashMap::new, (m, o) -> m.put(o.getName(), userInfo.getAge()), HashMap::putAll);
    System.out.println(nameMap);
}

运行程序,输出结果如下:

四、总结

​ 综合以上两种异常的出现情况和处理方法,考虑到代码的可读性,最终推荐通过如下方式使用 Collectors.toMap()

// 获取 姓名-年龄 映射
Map<String, Integer> nameMap = list.stream()
    .collect(Collectors.toMap(User::getName, o -> Optional.ofNullable(o.getAge()).orElse(-1), (v1, v2) -> v1));

整理完毕,完结撒花~





参考地址:

1.Java 8中Collectors.toMap空指针异常源码分析,https://blog.csdn.net/ranxiaotop/article/details/125896100

本文章已经生成可运行项目
<think>好的,用户之前问了如何用Java的Stream流修改List<Map<String, String>>中的Map内容,现在他们又转向了Collectors.toMap()使用方法。我需要先回顾之前的对话,确保这次回答与之前的建议一致,同时保持内容的连贯性。 用户可能是在处理集合转换时遇到了具体的问题,比如如何将列表转换为映射。他们可能已经尝试过使用Stream的map操作,现在需要更高级的收集器,比如toMap。需要明确toMap的常见用法、参数以及可能遇到的异常情况。 首先,我应该解释Collectors.toMap()的基本用法,包括键和值的提取函数。然后,举例说明不同情况下的使用,比如处理重复键的问题,这时候需要第三个参数来合并冲突的值。可能用户在实际使用中遇到了重复键导致的异常,所以需要强调如何处理这种情况。 另外,用户可能不知道如何指定特定的Map实现,比如TreeMap或LinkedHashMap,这里需要提到第四个参数的使用。同时,提醒他们在使用toMap时要注意空值和异常处理,比如NullPointerException,以及如何通过过滤来处理这些情况。 还需要考虑用户可能的错误使用场景,比如忘记处理重复键导致程序崩溃,或者对函数式接口的参数不熟悉。通过示例代码和关键点说明,帮助他们避免这些常见错误。 最后,总结使用时的最佳实践,比如始终处理重复键,选择适当的Map实现,以及处理空值的方法。确保用户能够全面理解toMap的用法,并且能够应用到实际项目中。</think>在Java中,`Collectors.toMap()` 是一个用于将流(Stream)元素转换为`Map`结构的收集器工具。以下是其核心用法和常见场景的详细说明: --- ### **基本语法** ```java Collectors.toMap( keyMapper, // 提取Map键的函数 valueMapper, // 提取Map值的函数 mergeFunction // (可选)键冲突时的合并策略 mapSupplier // (可选)指定Map的具体实现类 ) ``` --- ### **1. 最简用法(无重复键)** **场景**:将对象列表转换为`Map<Key, Value>`,假设键唯一。 ```java List<Person> people = ...; // 示例数据 Map<String, Integer> nameToAgeMap = people.stream() .collect(Collectors.toMap( Person::getName, // 键:Person对象的name字段 Person::getAge // 值:Person对象的age字段 )); ``` - **要求**:若存在重复的`name`,会直接抛出`IllegalStateException`。 --- ### **2. 处理重复键(必须指定合并策略)** **场景**:当键可能重复时,需通过第三个参数解决冲突。 ```java List<Person> people = ...; // 合并策略示例:取后出现的值覆盖前一个 Map<String, Integer> mergedMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldValue, newValue) -> newValue // 冲突时保留新值 )); // 其他合并策略示例: // (oldVal, newVal) -> oldVal + newVal // 值相加 // (oldVal, newVal) -> oldVal // 保留旧值 ``` --- ### **3. 指定Map的具体实现类** **场景**:需要生成`LinkedHashMap`或`TreeMap`等特定Map类型。 ```java Map<String, Integer> linkedHashMap = people.stream() .collect(Collectors.toMap( Person::getName, Person::getAge, (oldVal, newVal) -> newVal, LinkedHashMap::new // 指定为LinkedHashMap以保留插入顺序 )); ``` --- ### **4. 复杂键/值的构造** **场景**:键或值需要通过计算或组合生成。 ```java // 示例:将键拼接为"name-age",值为对象本身 Map<String, Person> complexKeyMap = people.stream() .collect(Collectors.toMap( p -> p.getName() + "-" + p.getAge(), // 自定义键 p -> p, // 值直接取对象 (p1, p2) -> p1 // 冲突时保留第一个 )); ``` --- ### **关键注意事项** 1. **空值(Null)处理**: - 若`keyMapper`或`valueMapper`返回`null`,会抛出`NullPointerException`。 - 可通过过滤空值避免异常: ```java .filter(p -> p.getName() != null) // 过滤键为null的情况 ``` 2. **线程安全**: - 默认生成的Map不保证线程安全(如`HashMap`),需自行处理同步。 3. **性能优化**: - 若数据量大且需排序,优先指定`TreeMap`并提前排序流。 --- ### **常见错误示例** ```java // 错误:未处理重复键,导致抛出IllegalStateException Map<String, Person> errorMap = people.stream() .collect(Collectors.toMap(Person::getName, p -> p)); // 修正:添加合并函数 Map<String, Person> correctMap = people.stream() .collect(Collectors.toMap( Person::getName, p -> p, (p1, p2) -> p1 )); ``` --- ### **总结** - **基本使用**:`toMap(keyMapper, valueMapper)`。 - **处理冲突**:必须添加合并函数(第三个参数)。 - **定制Map类型**:通过第四个参数指定`Map`实现类。 - **空值防御**:提前过滤或处理可能的`null`值。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值