Collectors.toMap 的正确使用方式

本文讲述了作者在使用Java8的流式编程时遇到的Bug,涉及`toMap`方法处理重复键的问题。通过深入源码分析,发现默认情况下`toMap`不合并重复键,导致异常。作者提供了修复方法并讨论了不同场景下如何选择合适的`mergeOperation`。
摘要由CSDN通过智能技术生成

引言

伴随着Java8的普及(有些小伙伴可能已经在玩Java17了,当然这不是重点),它为我引入了流式编程,由于本人在早些时候接触过Scala的函数式编程,所以非常喜欢一路点点的操作。

这不今天的Bug就这样产生了, 背景是因为本人在编程中多少有些代码洁癖,而且公司现在引入了一套代码质量评测平台,这样就不得不对已有的代码进行重构。经过本人一段时间的奋斗终于将2k+的code smell 降到了100以内, 项目的代码质量也在所属团队处于前列,一度使得我还得意了一阵子。好了闲话不说了, 接下来我们直接进入正题好了。

Bug 引入

在代码优化中, 我将已有的生成Map的操作改成了一下的样子。

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;

public class CollectorsToMapTest {

	@Test
	void test_toMap() {
		// 模拟制造重复数据
		List<Employee> employees = Stream.of(Employee.create("zhangsan", 18), Employee.create("zhangsan", 18))
				.collect(Collectors.toList());
		
		Map<String, Employee> employeeMap = employees.stream().collect(Collectors.toMap(Employee::getName, Function.identity()));
		System.out.println(employeeMap);
	}

	private static class Employee {
		private String name;
		private int age;

		private Employee(String name, int age) {
			this.name = name;
			this.age = age;
		}

		private static Employee create(String name, int age) {
			return new Employee(name, age);
		}
		
		

		public String getName() {
			return name;
		}

		public int getAge() {
			return age;
		}

		@Override
		public String toString() {
			return "Employee [name=" + name + ", age=" + age + "]";
		}

	}
}

执行如上代码,抛出异常

这个跟我们日常理解的Map存放键值对的操作相悖, 到底是怎么回事呢?

Bug 原因分析

跟入Collectors.toMap 内部查看源码, 俗话说万事不决,撸源码就是这个道理哈。

答案已经找到,源码诚不欺我。 toMap 方法默认给了一个不合并重复key的mergeOperation, 那这个mergeOperation 在哪用的呢?继续追打源码。

接着跟入m1.merge(), 选择HashMap

好了,HashMap 的调用mergeOperation的点已经找到了。

总结一下,Collector.toMap(keyGen, valGen) 默认不merge重复key的entry, 一旦遇到重复的item 会抛出IllegalStatusException Dulipcate key xxx, 特别注意这里的 xxx 是重复key是之前的value而不是重复的key, 这一句日志打的也很有迷惑性,如果不跟进源码很难理解其真正含义(跟领导解释好久,他不理解,就带他看源码了哦 --- Talk is cheap, show me the code)。

Bug Fix

既然知道根因了, 我们应该怎么去解决这个问题呢?

其实Collectors.toMap的重载方法已经给出了答案

将之前的Collector.toMap 改成如下的样子就OK了
Collectors.toMap(Employee::getName, Function.identity(), (prev, cur) -> cur)

(prev, cur) -> cur  ---- 这是个匿名函数,指出了merge操作该怎么进行。这里参考HashMap实现重复时用新值, 当然你可以像下面这样玩。

(prev, cur) -> prev  ---- 不更新

(prev, cur) -> prev+cur --- 累加value

...

具体就要业务逻辑而定了。

写在最后

既然不带mergeOperation的toMap操作会引起bug, 为什么还要提供呢?
我理解, 牛刀杀牛,鸡刀杀鸡了。 避免资源浪费,也避免到处都提供mergeFunction导致代码可读性不高。

总结下

> 对于key值不会重复的场景,用Collectors.toMap(keyGen, valGen)就可以了

> 对于key值可能会重复的场景,上Collectors.toMap(keyGen, valGen, mergeOperation)避免bug

key值不重复的场景一般有一下几种:

DB sequence      前置service已经去重,如查询接口     用户输入信息,保证去重时  ....

  • 27
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值