Java 8使用Stream转Map遇重复值抛出IllegalStateException问题
解决Java Stream转换Map时的Duplicate Key异常
当你用Java Stream将包含重复键的列表转换为Map时,默认的Collectors.toMap()方法会直接抛出IllegalStateException: Duplicate key异常——这正是你遇到的问题。咱们先看你的示例代码和对应的错误输出:
错误示例代码
List<Employee> empList = new ArrayList<Employee>(); Employee e1 = new Employee(1,"aaaa","mgr",100666.99); Employee e2 = new Employee(2,"bbbb","lead",90675.99); Employee e3 = new Employee(3,"cccc","dev",77555.99); Employee e4 = new Employee(4,"dddd","qe",63546.99); Employee e5 = new Employee(5,"eeee","lead",90675.99); Employee e6 = new Employee(6,"ffff","lead",90675.99); Employee e7 = new Employee(7,"gggg","dev",90675.99); Employee e8 = new Employee(8,"hhhh","qe",90675.99); empList.add(e1); empList.add(e2); empList.add(e3); empList.add(e4); empList.add(e5); empList.add(e6); empList.add(e7); empList.add(e8); List<Department> deptList = new ArrayList<Department>(); Department d1 = new Department(1, "IT", 10); Department d2 = new Department(2, "Sales", 20); Department d3 = new Department(3, "HR", 30); Department d4 = new Department(4, "Support", 40); deptList.add(d1); deptList.add(d2); deptList.add(d3); deptList.add(d4); System.out.println("\n\n List of Leads Names :"); List<String> empNames = empList.stream().filter(emp -> "lead".equals(emp.job)).map(Employee::getName).collect(Collectors.toList()); empNames.forEach(System.out::println); // 这里会抛出Duplicate Key异常 Map<String,Double> jobSalMap = empList.stream().filter(emp -> "lead".equals(emp.job)).collect(Collectors.toMap(emp->emp.job,emp->emp.sal)); jobSalMap.forEach(new BiConsumer<String,Double>(){ @Override public void accept(String arg0, Double arg1) { System.out.println(arg0 + " " + arg1); } }); double sumOfLeadSal = empList.stream().filter(emp -> "lead".equals(emp.job)).mapToDouble(Employee::getSal).sum(); System.out.println(sumOfLeadSal);
错误输出
List of Leads Names : bbbb eeee ffff Exception in thread "main" java.lang.IllegalStateException: Duplicate key 90675.99 at java.util.stream.Collectors.lambda$throwingMerger$0(Unknown Source) at java.util.HashMap.merge(Unknown Source) at java.util.stream.Collectors.lambda$toMap$58(Unknown Source) at java.util.stream.ReduceOps$3ReducingSink.accept(Unknown Source) at java.util.stream.ReferencePipeline$2$1.accept(Unknown Source) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) at java.util.stream.AbstractPipeline.copyInto(Unknown Source) at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source) at java.util.stream.AbstractPipeline.evaluate(Unknown Source) at java.util.stream.ReferencePipeline.collect(Unknown Source) at com.zzar.practice.TestEmployee.main(TestEmployee.java:59)
解决方案:添加合并函数
问题出在Collectors.toMap()的默认行为——当检测到重复键时,它会使用一个默认的"抛出异常"合并策略。要解决这个问题,只需要在toMap()方法中传入第三个参数:合并函数,用来定义遇到重复键时如何处理对应的value。
比如,如果你想保留最后一个遇到的value:
Map<String,Double> jobSalMap = empList.stream() .filter(emp -> "lead".equals(emp.job)) .collect(Collectors.toMap( emp -> emp.job, // key提取函数 emp -> emp.sal, // value提取函数 (oldVal, newVal) -> newVal // 合并策略:保留新值 ));
如果你想把相同key的所有薪资求和:
Map<String,Double> jobSalMap = empList.stream() .filter(emp -> "lead".equals(emp.job)) .collect(Collectors.toMap( emp -> emp.job, emp -> emp.sal, (oldVal, newVal) -> oldVal + newVal // 合并策略:薪资累加 ));
合并函数的逻辑完全可以根据你的业务需求自定义——比如把多个value收集到一个List里:
Map<String,List<Double>> jobSalMap = empList.stream() .filter(emp -> "lead".equals(emp.job)) .collect(Collectors.toMap( emp -> emp.job, emp -> new ArrayList<>(Collections.singletonList(emp.sal)), (oldList, newList) -> { oldList.addAll(newList); return oldList; } ));
这样修改后,代码就不会再抛出重复键的异常,而是按照你定义的规则处理重复的键值对。
内容的提问来源于stack exchange,提问作者Razzesh Ch
相关产品推荐
相关产品推荐

