You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用Java Stream高效实现两个Map<Long, Collection<Person>>的比较与重复人员数据过滤

优化Map比较逻辑:用Java Stream实现高效简洁的写法

首先,咱们先聊聊你原来代码里的几个问题:

  • 四层嵌套循环效率极低:时间复杂度是O(MNP*Q)(M是currMap的entry数,N是prevMap的entry数,P/Q是对应entry里的Person数量),当数据量稍大时性能会急剧下降。
  • 逻辑判断错误:你的条件(!currPerson.getName().equals(prevPerson.getName()) && !currPerson.getSurname().equals(prevPerson.getSurname()))是说「name不同并且surname不同」才添加Person,这和你的需求完全相反——你需要排除的是「name和surname都相同」的Person,正确逻辑应该是:只要prev中存在任何一个和currPerson的name+surname都匹配的Person,就不保留这个currPerson。
  • 结果收集逻辑错误:你在循环外创建了一个全局的arr,会把所有符合条件的Person都塞进同一个列表,然后重复put同一个key,导致不同key下的Person混在一起,结果完全不符合预期。

高效优化方案思路

核心优化点是提前把prevMap中所有Person的唯一标识(name+surname)存入一个Set,这样后续判断是否存在匹配的时间复杂度是O(1),整体时间复杂度降到O(P + C)(P是prev中总Person数,C是curr中总Person数),效率提升非常明显。

然后用Stream API来实现过滤和收集,代码会更简洁易读。

具体实现代码

第一步:定义Person的唯一标识类(或用Record)

为了把name和surname作为一个整体存入Set,我们需要一个能正确实现equals()和hashCode()的类。Java 16+可以用Record(简洁方便),低于16的话可以自定义类:

// Java 16+ 推荐用Record,自动实现equals和hashCode
private record PersonKey(String name, String surname) {}

// 兼容Java 16以下的自定义类
/*
private static class PersonKey {
    private final String name;
    private final String surname;

    public PersonKey(String name, String surname) {
        this.name = name;
        this.surname = surname;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PersonKey personKey = (PersonKey) o;
        return Objects.equals(name, personKey.name) && Objects.equals(surname, personKey.surname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, surname);
    }
}
*/

第二步:提取prevMap的Person标识到Set

Set<PersonKey> prevPersonKeys = prevMap.values().stream()
    .flatMap(Collection::stream) // 把所有entry里的Collection扁平化成Person流
    .map(p -> new PersonKey(p.getName(), p.getSurname())) // 转成PersonKey
    .collect(Collectors.toSet()); // 存入Set,自动去重

第三步:用Stream处理currMap生成结果

Map<Long, Collection<Person>> result = currMap.entrySet().stream()
    .collect(Collectors.toMap(
        Map.Entry::getKey, // 保留原key
        entry -> entry.getValue().stream()
            .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname()))) // 过滤掉在prev中存在的Person
            .collect(Collectors.toList()) // 收集成List,也可以用toCollection指定集合类型
    ));

如果需要过滤掉结果中为空的Collection(比如某个key下所有Person都被排除了,就不保留这个key),可以加一步过滤:

Map<Long, Collection<Person>> result = currMap.entrySet().stream()
    .map(entry -> Map.entry(
        entry.getKey(),
        entry.getValue().stream()
            .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname())))
            .collect(Collectors.toList())
    ))
    .filter(entry -> !entry.getValue().isEmpty()) // 去掉空集合的entry
    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

完整示例测试

结合你给出的示例数据,完整代码如下:

import java.util.*;
import java.util.stream.Collectors;

public class PersonMapComparison {

    static class Person {
        private String name;
        private String surname;
        private int age;
        private String gender;

        public Person(String name, String surname, int age, String gender) {
            this.name = name;
            this.surname = surname;
            this.age = age;
            this.gender = gender;
        }

        public String getName() { return name; }
        public String getSurname() { return surname; }

        @Override
        public String toString() {
            return "Person{name='" + name + "', surname='" + surname + "', age=" + age + ", gender='" + gender + "'}";
        }
    }

    private record PersonKey(String name, String surname) {}

    public static void main(String[] args) {
        // 初始化示例数据
        Collection<Person> currPersons = List.of(new Person("John", "Smith", 20, "m"));
        Map<Long, Collection<Person>> currMap = Map.of(1L, currPersons);

        Collection<Person> prevPersons = List.of(
            new Person("John", "Smith", 20, "m"),
            new Person("Sarah", "Smith", 27, "f")
        );
        Map<Long, Collection<Person>> prevMap = Map.of(1L, prevPersons);

        // 提取prev的Person标识
        Set<PersonKey> prevPersonKeys = prevMap.values().stream()
            .flatMap(Collection::stream)
            .map(p -> new PersonKey(p.getName(), p.getSurname()))
            .collect(Collectors.toSet());

        // 生成结果
        Map<Long, Collection<Person>> result = currMap.entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry -> entry.getValue().stream()
                    .filter(p -> !prevPersonKeys.contains(new PersonKey(p.getName(), p.getSurname())))
                    .collect(Collectors.toList())
            ));

        // 打印结果(此时结果中key=1的集合为空,因为John Smith在prev中存在)
        result.forEach((key, persons) -> {
            System.out.println("Key: " + key);
            persons.forEach(System.out::println);
        });
    }
}

额外说明

  • 如果你的Person类本身的equals()和hashCode()就是基于name和surname实现的(不包含age、gender等其他字段),那可以直接把Person存入Set,不需要额外定义PersonKey。
  • 如果需要保留原Collection的类型(比如LinkedList而不是ArrayList),可以把Collectors.toList()换成Collectors.toCollection(LinkedList::new)。

内容的提问来源于stack exchange,提问作者cutelittlegnome

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 16:54:06