java.lang.IllegalStateException: Duplicate key

序言

最近监控扫描出我们项目的某些异常信息,报错java.lang.IllegalStateException: Duplicate key xxx,看到异常来自stream流,然后定位看了一下是某位同事的代码使用stream流把List转Map集合出现重复的key异常信息。List集合A对象来源于某个接口的返回,使用A对象的uuid成员变量作为key,理论上uuid作为唯一标识不应该有重复。
所以正确的做法是:
1)找该接口对应责任人,定位看List对象A的uuid为什么出现重复;
2)查看本项目代码中的异常来源;

java.util.stream.Collectors

Java 8版本引入Stream流式数据处理方式,使得可以对集合进行更简单高效的操作。
API文档链接如下:

异常根因

首先看一段示例代码,

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class DuplicateKeyDemo {

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Builder
    private static class Employee {
        private int identifierId;

        private String name;

        private int age;
    }

    public static void main(String[] args) {
        List<Employee> employList = new ArrayList<>();
        employList.add(new Employee(1, "Mike", 25));
        employList.add(new Employee(2, "Mary", 26));
        employList.add(new Employee(3, "Jack", 28));
        employList.add(new Employee(4, "Tom", 23));
        employList.add(new Employee(5, "Lucy", 21));
        employList.add(new Employee(6, "Jim", 26));
        employList.add(new Employee(7, "David", 29));
        employList.add(new Employee(8, "Jack", 22));
        employList.add(new Employee(8, "Jack", 25));
        Collector<Employee, ?, Map<String, Integer>> collector = Collectors.toMap(Employee::getName, Employee::getAge);
        Map<String, Integer> nameAgeIgnoreRepeatMap = employList.stream().collect(collector);
        System.out.println(nameAgeIgnoreRepeatMap);
    }
}

抛出的异常信息如下,

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 28
	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:482)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
	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.hust.zhang.stream.DuplicateKeyDemo.main(DuplicateKeyDemo.java:43)

直接点到HashMap的merge方法时,Map中已经存在28岁的Jack,新来的一条数据是22岁的Jack,那么在执行remappingFunction.apply(old.value, value)时就会报错java.lang.IllegalStateException: Duplicate key 28
在这里插入图片描述
HashMap是基于数组+链表+红黑树的数据结构(可以参考之前的文章了解数据结构:红黑树与AVL树的区别),此时Node节点的Key冲突,Node节点的Value值应该取新值还是旧值?没有进行规约就会抛出IllegalStateException异常。

解决方案

如果假设我们在这个Map里就是可以被覆盖,那么应该怎么做?

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class DuplicateKeyDemo {

    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Builder
    private static class Employee {
        private int identifierId;

        private String name;

        private int age;
    }

    public static void main(String[] args) {
        List<Employee> employList = new ArrayList<>();
        employList.add(new Employee(1, "Mike", 25));
        employList.add(new Employee(2, "Mary", 26));
        employList.add(new Employee(3, "Jack", 28));
        employList.add(new Employee(4, "Tom", 23));
        employList.add(new Employee(5, "Lucy", 21));
        employList.add(new Employee(6, "Jim", 26));
        employList.add(new Employee(7, "David", 29));
        employList.add(new Employee(8, "Jack", 22));
        employList.add(new Employee(8, "Jack", 25));
        Collector<Employee, ?, Map<String, Integer>> antiCollisionCollector = Collectors.toMap(Employee::getName,
                Employee::getAge, (oldValue, newValue) -> oldValue);
        Map<String, Integer> nameAgeMap = employList.stream().collect(antiCollisionCollector);
        System.out.println(nameAgeMap);
    }
}

针对原来代码改了一行,在Collectors.toMap方法后面追加(oldValue, newValue) -> oldValue,表示当出现冲突时取初始出现值,如下图,执行remappingFunction.apply(old.value, value)方法时返回初始值28
在这里插入图片描述
如果改成(oldValue, newValue) -> newValue则在本示例中返回重复对象的末尾值25

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值