Collectors.toMap() 抛出 “Duplicate key ***” 异常,异常堆栈如下:
java.lang.IllegalStateException: Duplicate key *** at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) at java.util.HashMap.merge(HashMap.java:1253) 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:1374) 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)
错误分析:
这个错误信息表明在使用 Java Stream API 的 Collectors 进行流操作时遇到了一个问题。具体来说,java.util.stream.Collectors.lambda$throwingMerger 指的是在合并收集操作(比如toMap)中的元素时,由于使用了抛出异常的合并器(throwingMerger),而实际上出现了重复的键(key)。java.lang.IllegalStateException: Duplicate key *** 指出键 "***" 在收集过程中重复出现了,而合并策略是抛出 IllegalStateException 异常。
错误示例回放:
下面提供一个错误示例,该示例将抛出上述错误信息,代码如下:
package com.hxstrive.demo202406; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Demo20240618143339 { public static void main(String[] args) { List<Item> getFunctionList = new ArrayList<>(); getFunctionList.add(new Item("app1", "应用1")); getFunctionList.add(new Item("app2", "应用2")); getFunctionList.add(new Item("app1", "应用3")); // 执行 toMap 操作 Map<String,String> map = getFunctionList.stream() .collect(Collectors.toMap(Item::getAppId, Item::getType)); System.out.println(map); } // 内部类 private static class Item { private String appId; private String type; public Item(String appId, String type) { this.appId = appId; this.type = type; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getType() { return type; } public void setType(String type) { this.type = type; } } }
运行上述示例,抛出如下错误信息:
Exception in thread "main" java.lang.IllegalStateException: Duplicate key 应用1 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.hxstrive.demo202406.Demo20240618143339.main(Demo20240618143339.java:21)
解决问题:
在 Java Stream API 中,Collectors.toMap 方法用于将流中的元素收集到一个 Map 中。如果你使用的是一个没有指定合并函数的 toMap 重载方法,并且流中的元素有重复的键,那么将会抛出 IllegalStateException 异常,因为默认的合并策略是不允许重复的。
为了解决这个问题,你可以提供一个合并函数,来定义当键冲突时如何合并值。例如:
Map<KeyType, ValueType> map = stream.collect(Collectors.toMap( element -> element.getKey(), // 键的映射函数 element -> element.getValue(), // 值的映射函数 (existingValue, newValue) -> existingValue // 合并函数,决定如何处理重复的键 ));
上述代码中,合并函数 (existingValue, newValue) -> existingValue 简单地保留了现有的值,并丢弃了新值。你可以根据需要自定义合并函数的行为。
重写示例 Demo20240618143339,代码如下:
package com.hxstrive.demo202406; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class Demo20240618143339 { public static void main(String[] args) { List<Item> getFunctionList = new ArrayList<>(); getFunctionList.add(new Item("app1", "应用1")); getFunctionList.add(new Item("app2", "应用2")); getFunctionList.add(new Item("app1", "应用3")); Map<String,String> map = getFunctionList.stream() .collect(Collectors.toMap(Item::getAppId, Item::getType, // 这是合并函数,保留旧值 (oldValue, newValue) -> oldValue)); System.out.println(map); } private static class Item { private String appId; private String type; public Item(String appId, String type) { this.appId = appId; this.type = type; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getType() { return type; } public void setType(String type) { this.type = type; } } }
运行示例,输出如下:
{app2=应用2, app1=应用1}