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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:11:53