写点什么

活久见,java8 lamdba Collectors.toMap() 报 NPE

  • 2023-04-14
    湖南
  • 本文字数:1700 字

    阅读完需:约 6 分钟

事情是这样的,今天调试程序时,有个功能是需要将查询的结果集:List<Map<String, Object>> 中的 key 转换成 java 的驼峰命名规则模式,比如:USER_NAME => userName 。代码如下:

List<Map<String, Object>> result = query();result = result.stream().map(m -> m.entrySet().stream().collect(Collectors.toMap(e -> JavaUtil.getJavaModeColumnName(e.getKey()), Map.Entry::getValue)))                    .collect(Collectors.toList());
复制代码

然后就遇到了如下报错信息:

一开始的时候还挺郁闷的,用了这么 lamdba 的 API,也没遇到这个错误啊,这是为什么呢?我们去查看这行:


HashMap.merge(HashMap.java:1225) ~[na:1.8.0_201]的源代码是什么:

判断是:如果 value==null,就抛出 NPE。

过程分析

复现

Map<String, Object> objectMap = new HashMap<>();objectMap.put("ADC_ADC", null);// 报错: value 不能为空objectMap.entrySet().stream().collect(    Collectors.toMap(e -> e.getKey().toLowerCase(), Map.Entry::getValue));
复制代码

上述代码复现了我们上面提到的原始问题出现的错误,我们分析一下调用栈和查看 jdk 源码就会发现:

Collectors.toMap 中调用了这个方法:

而上述方法中的 BiConsumer<M, T> accumulator 调用了 map.merge()。


问题找到了,那么怎么解决呢?

处理方式

既然是 accumulator 方法有问题,那么我们就替换掉 accumulator。我们找一下 Collectors 类中的 toMap()方法并没有提供可以传入 accumulator 参数的方法。那么我们退而求其次,从 collect()方法下手,代码如下:

Map<String, Object> objectMap = new HashMap<>();objectMap.put("USER_NAME", null);
Map<String, Object> collect = objectMap.entrySet().stream() .collect(HashMap::new, (map, entry) -> map.put(Util.getJavaModeColumnName(entry.getKey()), entry.getValue()), HashMap::putAll);
复制代码

R collect(Supplier supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);


还函数的参数都是什么意思呢:

supplier: 用于创建容器的工厂函数。在收集元素之前,该函数将被调用一次,用于创建一个空的容器。

accumulator: 用于将 Stream 中的元素添加到容器中的累加器函数。该函数接受两个参数,第一个参数是容器,第二个参数是 Stream 中的元素。该函数将 Stream 中的每个元素添加到容器中。


combiner: 用于合并两个容器的函数。在多个线程并行执行收集操作时,将在每个线程中创建一个容器,并使用该函数将它们合并为一个容器。如果 Stream 是串行的,该函数将不会被调用。


那么在我们使用 lamdba 的收集器,jdk 自带的不能满足我们的需求时,我们就可以使用该方法自定义手机规则和模式。


官方示例:String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,StringBuilder::append).toString(); 将数据拼接 。


运行结果如下图:

这种方式的问题是:就是当你的 key 值重复的时候,这将会覆盖上一个值。但是在我当前的使用场景下是不存在的,因为表名称字段肯定是唯一的。

其他

网上有讨论说:更新 JDK,我用 JDK16,JDK17 时再次测试改代码时,都无法得到正确的答案。

遇到这个问题时,我就在想,java 为什么要如此设计?value 的值为什么不能为 null?

后来我搜索了相关问题的讨论,在 Stack Overflow 上找到了这篇:

stackoverflow.com/questions/4…

其中有一段讨论是这么说的:

大意就是:没想到 null 值对 API 的影响如此之大,有人则认为这是一个 jdk 的缺陷。


这之后我不死心,之后我找到了在官网上的讨论的:bugs.openjdk.org/browse/JDK-…


不幸的是:目前该 bug 并未被解决,不论出于什么原因。这与我在 JDK16,JDK17 下的测试是保持一致的。

后续

大家也可以看一下下面这个示例的返回值是什么:

List<User> list = new ArrayList<>();list.add(new User(12, "张三"));list.add(new User(16, "李四"));list.add(new User(16, "王五"));Map<Integer, String> collect1 = list.stream().    collect(Collectors.toMap(User::getAge, User::getName));
复制代码

作者:阿 Qoder

链接:https://juejin.cn/post/7221470013872980024

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
活久见,java8 lamdba Collectors.toMap()报NPE_做梦都在改BUG_InfoQ写作社区