
本文介绍在 java 中根据多个字段(如 firstname 和 lastname)对对象列表进行去重的实用方法,使用 stream api 结合 collectors.tomap 实现精准去重,保留每组重复项中的任意一个实例。
本文介绍在 java 中根据多个字段(如 firstname 和 lastname)对对象列表进行去重的实用方法,使用 stream api 结合 collectors.tomap 实现精准去重,保留每组重复项中的任意一个实例。
在实际开发中,我们常需对 List<T> 进行去重,但标准的 distinct() 方法仅依赖 equals()/hashCode(),无法满足“按指定多个属性组合去重”的需求。例如,当 Person 对象需依据 firstName 和 lastName 唯一,而忽略 age 时,直接重写 Person 的 equals() 会破坏业务语义(比如其他场景仍需按全部字段判断相等)。此时,推荐使用 基于复合键的流式去重策略。
核心思路是:将多属性组合为唯一键(如 List<String> 或自定义键类),再通过 Collectors.toMap() 实现“遇重复键则保留第一个值”的逻辑。以下是完整可运行的解决方案:
import java.util.*;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Mike", "Doe", 25);
Person p2 = new Person("Mike", "Doe", 54);
Person p3 = new Person("John", "Doe", 61);
Person p4 = new Person("Barry", "Adams", 61);
List<Person> people = new ArrayList<>(Arrays.asList(p1, p2, p3, p4));
// ✅ 正确做法:按 firstName + lastName 去重,保留首个匹配对象
List<Person> uniquePeople = people.stream()
.collect(Collectors.toMap(
p -> Arrays.asList(p.getFirstName(), p.getLastName()),
p -> p,
(existing, replacement) -> existing // 冲突时保留已存在者(即第一个)
))
.values()
.stream()
.toList(); // Java 16+;若用旧版,替换为 .collect(Collectors.toList())
System.out.println(uniquePeople);
// 输出: [Mike Doe, John Doe, Barry Adams]
}
}⚠️ 注意事项:
- toMap() 的 merge 函数 (existing, replacement) -> existing 确保首次出现的 Person 被保留,后续同名者被丢弃;
- 键类型推荐使用 List<String>(简洁)或自定义不可变键类(更类型安全、可重用),避免使用字符串拼接(如 fName + "|" + lName)以防分隔符冲突;
- 若只需姓名字符串而非 Person 对象,可简化为 people.stream().map(Person::toString).collect(Collectors.toSet()),但该方式丢失原始对象引用,且不保证顺序(LinkedHashSet 可保序);
- 该方案时间复杂度为 O(n),空间复杂度为 O(n),优于嵌套循环的 O(n²) 方案。
总结:面对多属性去重需求,应避免修改领域对象的 equals(),而是借助 Collectors.toMap() 构建临时复合键映射——既保持代码清晰性,又具备良好的可维护性与扩展性。
立即学习“Java免费学习笔记(深入)”;


















