Java 8中基于两个属性去除对象列表重复项的实现方案
嘿,刚好之前处理过类似的场景,我来给你详细说下怎么用Predicate实现双属性去重!
Java 8里单属性去重的distinctByKey方法大家都熟,但要基于两个属性的组合去重,核心思路其实是把这两个属性打包成一个唯一的“组合键”,然后用一个集合来跟踪已经出现过的键,Predicate负责判断当前对象的组合键是否已经存在。
通用工具方法实现
首先我们可以写一个通用的工具方法,生成用于双属性去重的Predicate,这个方法可以复用在任何需要双属性去重的场景:
import java.util.AbstractMap; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; public class StreamUtils { public static <T> Predicate<T> distinctByTwoProperties(Function<T, Object> keyExtractor1, Function<T, Object> keyExtractor2) { // 用ConcurrentHashMap的keySet来跟踪已出现的组合键,线程安全;如果不需要线程安全,换成new HashSet<>()就行 Set<Object> seen = ConcurrentHashMap.newKeySet(); return t -> { // 提取两个属性的值 Object key1 = keyExtractor1.apply(t); Object key2 = keyExtractor2.apply(t); // 用AbstractMap.SimpleEntry打包成组合键,它已经正确实现了equals和hashCode方法 Object combinedKey = new AbstractMap.SimpleEntry<>(key1, key2); // add方法会返回布尔值:如果键不存在就添加并返回true,存在就返回false return seen.add(combinedKey); }; } }
示例代码演示
1. 自定义Customer类
先定义你的Customer对象,包含customerName和DOB两个核心属性:
import java.time.LocalDate; class Customer { private String customerName; private LocalDate DOB; // 构造器 public Customer(String customerName, LocalDate DOB) { this.customerName = customerName; this.DOB = DOB; } // Getter方法 public String getCustomerName() { return customerName; } public LocalDate getDOB() { return DOB; } // 重写toString方便打印查看 @Override public String toString() { return "Customer{name='" + customerName + "', DOB=" + DOB + "}"; } }
2. 使用Stream+Predicate去重
接下来就可以用上面的工具方法,结合Stream API完成去重:
import java.time.LocalDate; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { // 构造包含重复项的Customer列表 List<Customer> customers = Arrays.asList( new Customer("Alice", LocalDate.of(1990, 1, 1)), new Customer("Bob", LocalDate.of(1985, 5, 15)), new Customer("Alice", LocalDate.of(1990, 1, 1)), // 重复项:名字和生日都相同 new Customer("Alice", LocalDate.of(1991, 2, 2)), // 名字相同但生日不同,不算重复 new Customer("Bob", LocalDate.of(1985, 5, 15)) // 重复项 ); // 调用工具方法生成Predicate,过滤重复项 List<Customer> uniqueCustomers = customers.stream() .filter(StreamUtils.distinctByTwoProperties(Customer::getCustomerName, Customer::getDOB)) .collect(Collectors.toList()); // 打印结果,会看到重复项被移除了 uniqueCustomers.forEach(System.out::println); } }
额外说明
- 如果觉得
AbstractMap.SimpleEntry不够直观,你也可以自定义一个不可变的记录类(Java 16+支持)来打包组合键,比如:
然后把工具方法里的record CustomerUniqueKey(String customerName, LocalDate dob) {}combinedKey换成new CustomerUniqueKey((String)key1, (LocalDate)key2),可读性会更好。 - 要确保你提取的两个属性对象已经正确重写了
equals()和hashCode()方法,比如String、LocalDate这些JDK自带类都是没问题的;如果是自定义属性,一定要手动实现这两个方法,否则组合键的判断会出错。
内容的提问来源于stack exchange,提问作者Pavan ks
相关产品推荐
相关产品推荐

