活久见,java8 lamdba Collectors.toMap()报NPE
前言
事情是这样的,今天调试程序时,有个功能是需要将查询的结果集: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));
转载自:https://juejin.cn/post/7221470013872980024