如何使用Java 8 Stream获取按所有字段去重的对象列表?
你遇到的问题其实很典型——distinct()方法默认依赖对象的equals()和hashCode()方法判断相等性,而默认的Object.equals()只会比较对象的引用地址,所以哪怕两个Person的字段完全一致,它们也会被当成不同对象,导致distinct()没起作用。
下面给你几种可行的解决方案:
方案一:重写Person类的equals()和hashCode()方法(推荐)
这是Java中判断对象逻辑相等的标准做法,你需要让equals()对比所有字段,hashCode()根据所有字段生成哈希值。
手动实现示例
import java.util.Objects; public class Person { private String name; private String age; // 构造函数、getter、setter省略 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return Objects.equals(name, person.name) && Objects.equals(age, person.age); } @Override public int hashCode() { return Objects.hash(name, age); } }
用Lombok简化(如果项目已引入Lombok)
加一个@EqualsAndHashCode注解就能自动生成基于所有字段的equals和hashCode方法,省去手动编写的麻烦:
import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; @Getter @Setter @EqualsAndHashCode public class Person { private String name; private String age; }
完成这一步后,你原来的代码personList.stream().distinct().collect(Collectors.toList())就能正常去重了——因为现在字段完全相同的Person会被判定为相等。
方案二:不修改Person类的情况下实现去重
如果Person是第三方类,你没法修改它的代码,可以试试这两种方式:
方式1:用TreeSet结合自定义比较器
TreeSet可以根据自定义比较器判断对象是否相等,我们可以把所有字段组合成比较逻辑:
import java.util.Comparator; import java.util.List; import java.util.TreeSet; import java.util.stream.Collectors; List<Person> uniquePersons = personList.stream() .collect(Collectors.toCollection(() -> new TreeSet<>( Comparator.comparing(Person::getName) .thenComparing(Person::getAge) ))) .stream() .collect(Collectors.toList());
注意:TreeSet是有序集合,且如果字段存在null值,需要额外用Comparator.nullsFirst()或nullsLast()处理。
方式2:用Filter结合HashSet跟踪已处理对象
我们可以用HashSet记录已经见过的对象,配合filter实现去重:
import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; Set<Person> seen = new HashSet<>(); List<Person> uniquePersons = personList.stream() .filter(p -> seen.add(p)) .collect(Collectors.toList());
如果Person的equals和hashCode方法无法修改,你可以先把Person转换成包含所有字段的自定义Key对象(比如Java 16+的record),再用Key来跟踪:
import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; // Java 16+ 用record定义Key,低版本可以用普通类 record PersonKey(String name, String age) {} Set<PersonKey> seenKeys = new HashSet<>(); List<Person> uniquePersons = personList.stream() .filter(p -> seenKeys.add(new PersonKey(p.getName(), p.getAge()))) .collect(Collectors.toList());
总结
最推荐方案一,因为它符合Java对象相等性的设计规范,后续把对象放入Set、Map等场景也能正常工作。如果实在不能修改Person类,再考虑方案二的两种方式。
内容的提问来源于stack exchange,提问作者Nazerke

