Stream中Collectors.toMap方法的常见疑惑

2个月前发布 gsjqwyl
18 0 0

文章标题:

Stream中Collectors.toMap方法的常见困惑

文章内容:

文章目录

  • 一、普遍问题
  • 二、键重复情况
    • 2.1、报错实例
    • 2.2、解决途径
  • 三、值为空情况
    • 3.1、报错实例
    • 3.2、解决办法
    • 3.1、方案其一
    • 3.2、方案其二

一、普遍问题

关于stream的Collectors.toMap()方法的常见状况:
1、在将List转换为Map时,若键存在重复,就会出现报错。例如java.lang.IllegalStateException: Duplicate key这样的错误;
2、当值为null时,会引发空指针异常,即java.lang.NullPointerException。

第一种状况是由于在把List转换成Map的过程中,Map集合里的键出现了重复;

第二种状况是因为在List转Map时,Map集合里的值存在null,当使用Collectors.toMap()时就会报空指针,这是因为底层调用了Map的merge方法,而map规范里规定此处的值不能为null,所以就抛出了空指针异常。

二、键重复情况

详细内容可参考《stream流Collectors.toMap(),键值重复状况

2.1、报错实例

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        User u1 = new User("张三", "男");
        User u2 = new User("张三", "女");
        list.add(u1);
        list.add(u2);
        // 键:姓名  值:性别
        Map<String, String> userMap = list.stream().collect(Collectors.toMap(User::getName, User::getSex));
        System.out.println(userMap);
    }
}

@Data
@AllArgsConstructor
class User {
    // 姓名
    private String name;
    // 性别
    private String sex;
}

报错信息:

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 男
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1254)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.springboot.store.Test.main(Test.java:17)

2.2、解决途径

通过Collectors.toMap指定merge函数
能够自定义一个merge函数来确定键重复时如何获取值。比如下面这种写法,是保留最后一个值。当然也可以保留第一个,或者进行更复杂的处理。

// 键:姓名  值:性别
Map<String, String> userMap = list.stream().collect(Collectors.toMap(User::getName, User::getSex, (oldValue, newValue) -> newValue));
System.out.println(userMap);

三、值为空情况

详细内容可参考《stream流的Collectors.toMap方法值为null时的解决方式

3.1、报错实例

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test {
    public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        User u1 = new User("张三", null);
        list.add(u1);
        // 键:姓名  值:性别
        Map<String, String> userMap = list.stream().collect(Collectors.toMap(User::getName, User::getSex, (o1, o2) -> o1));
        System.out.println(userMap);
    }
}

@Data
@AllArgsConstructor
class User {
    // 姓名
    private String name;
    // 性别
    private String sex;
}

报错信息:

Exception in thread "main" java.lang.NullPointerException
    at java.util.HashMap.merge(HashMap.java:1225)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at com.springboot.store.Test.main(Test.java:17)

3.2、解决办法

3.1、方案其一

利用Optional类来处理值,优点是还能继续使用Collectors.toMap,缺点是为null的值会被更改,这可能不符合业务上的预期。

3.2、方案其二

使用Stream#collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner)方法,能够保留为null的值。所以个人更推荐方案二。

以上两种解决方法的代码如下:

// 解决方案一,使用Optional类处理null  若值为空,设置默认值""
HashMap<String, String> map02 = list.stream()
    .collect(Collectors.toMap(User::getName, s -> Optional.ofNullable(s.getSex()).orElse(""), (a, b) -> b, HashMap::new));
System.out.println(map02);

// 解决方案二,直接使用collect()方法进行规约操作  调用hashMap putAll方法, 注意键相同时,值会覆盖。
HashMap<String, String> map03 = list.stream()
    .collect(HashMap::new, (map, item) -> map.put(item.getName(), item.getSex()), HashMap::putAll);
System.out.println(map03);
© 版权声明

相关文章

没有相关内容!

暂无评论

none
暂无评论...