Collectors.toMap() 抛出 “Duplicate key ***” 异常

Collectors.toMap() 抛出 “java.lang.IllegalStateException: Duplicate key ***” 异常。

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}
与肝胆人共事,无字句处读书。——周恩来
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号