Detailed solution to the java.lang.IllegalStateException: Duplicate key xxx error caused by Collectors.toMap in java

1. Reproducing the error


Today, the test pointed me to a formal environment on ZenTao bug, as shown in the figure below:

Insert image description here

That is java.lang.IllegalStateException: Duplicate key 2.

2. Analysis errors


As java.lang.IllegalStateException: Duplicate key 2you can see, this is an javaexisting error that is thrown 2个重复键.

If you want to figure out the cause of the error, analyze it through the following steps:

  1. First, when you see this error message, first use and test this interface locally postman, as shown in the figure below:

Insert image description here

Because it involves company security, the interface cannot be displayed.

As can be seen from the picture above, there is no problem with the local environment.

  1. View the logs of the formal environment, as shown below:

Insert image description here

It can be seen from the error 第2行that this is stream.Collectorsthe error caused.

Continue looking down and find the red box, which is the location where the project code error is thrown.

So, view the code online as follows:

xxxRepository.list(new xxxQuery().setXxx(xxxId))
                .stream()
                .collect(Collectors.toMap(xxxEntity::getName, xxxEntity::getId));

Due to the confidentiality mechanism, the actual name xxxhas been replaced with .

Guess from xxxEntity::getNamethe code: It may be that there are two identical names in the database, which Mapwill be the same when converted Key, resulting in hashan exception, as shown in the following code:

@Data
@NoArgsConstructor
public class Student {
  private BigDecimal score;
  private String name;
  private Integer id;
  private Integer age;

  @Override
  public String toString() {
    return JSON.toJSONString(this);
  }

  public Student(BigDecimal score, String name, Integer id, Integer age) {
    this.score = score;
    this.name = name;
    this.id = id;
    this.age = age;
  }

  public static void main(String[] args) {
    Student student1 = new Student(new BigDecimal(99.5), "陈希尔", 1, 12);
    Student student2 = new Student(new BigDecimal(88), "陈希尔", 2, 12);

    List<Student> list = new ArrayList();
    list.add(student1);
    list.add(student2);

    Map<String, Integer> collect =
        list.stream().collect(Collectors.toMap(Student::getName, Student::getId));
    Set<String> keys = collect.keySet();
    String sout = "key = %s, value = %s \n";
    keys.forEach(t -> System.out.format(sout, t, collect.get(t)));
  }
}

Pay attention to the above sample code, the following is also modified based on this sample code.

The output is as shown below:

Insert image description here

  1. Query data in the database based on the guess, as follows:
select
    COUNT(`name`) as nameCount,
    `name`
from
    xxx_table
GROUP BY
    `name`
HAVING
    nameCount >= 2;

Insert image description here

I found the problem, and it turned out that it was namerepeated, causing this error.

In actual application development, we often convert a Listquery data set into a query data set Map. So what we do here list.stream().collect()is actually doing such a thing. It is java8implemented in a way thatstream it is composed of objects .typekeyentityvalueMap

In this error type, namethe value mapin keycannot be repeated. Repeating will cause hashan exception.

3. Solve the problem


Now that we know the cause of the problem, we can solve it in the following ways:

  1. Remove nameduplicate records

  2. Modify the code to ensure that even if there are duplicate values, they can be converted Map.

We just need to add this line of code:

 Map<String, Integer> collect =
        list.stream().collect(Collectors.toMap(Student::getName, Student::getId));

Just modify it as follows:

 Map<String, Integer> collect =
        list.stream()
            .collect(
                Collectors.toMap(Student::getName, Student::getId, (entity1, entity2) -> entity1));

This code is equivalent to:

Map<String, Integer> collect =
        list.stream()
            .collect(
                Collectors.toMap(
                    Student::getName,
                    Student::getId,
                    new BinaryOperator<Integer>() {
    
    
                      @Override
                      public Integer apply(Integer entity1, Integer entity2) {
    
    
                        return entity1;
                      }
                    }));

After modification, the test results are as follows:

Insert image description here

4. Important additions

 Map<String, Integer> collect =
        list.stream()
            .collect(
                Collectors.toMap(Student::getName, Student::getId, (entity1, entity2) -> entity1));

Equivalent to:

Map<String, Integer> collect =
        list.stream()
            .collect(
                Collectors.toMap(
                    Student::getName,
                    Student::getId,
                    new BinaryOperator<Integer>() {
    
    
                      @Override
                      public Integer apply(Integer entity1, Integer entity2) {
    
    
                        return entity1;
                      }
                    }));

We toMapcan find out by looking at the source code, as shown below:

Insert image description here

Its third parameter is a functional interface, so we can use lamdaexpressions to simplify the code.

Guess you like

Origin blog.csdn.net/lvoelife/article/details/133378764