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

如何实现Java Stream按可变字段动态去重?

动态字段去重的Java实现方案

问题根源分析

你当前的代码无法正常工作的核心原因:你试图传递字符串"p.getName() + p.getPrice()"作为键提取逻辑,但Java是静态类型语言,无法直接将字符串解析为可执行的Lambda表达式。distinctByKey(p -> key1)实际上是把每个Pojo对象都映射到同一个字符串常量,导致所有元素被判定为重复,最终只保留第一个元素。


可行实现方案

方案1:枚举定义固定去重维度(类型安全,性能最优)

适合提前确定所有可能的去重规则,代码可读性和安全性最高。

首先定义枚举管理去重键:

enum DeduplicationKey {
    NAME_PRICE(p -> p.getName() + "_" + p.getPrice()),
    REF1(Pojo::getRef1),
    NAME(Pojo::getName),
    PRICE(Pojo::getPrice);

    private final Function<Pojo, Object> keyExtractor;

    DeduplicationKey(Function<Pojo, Object> keyExtractor) {
        this.keyExtractor = keyExtractor;
    }

    public Function<Pojo, Object> getKeyExtractor() {
        return keyExtractor;
    }
}

修改测试代码,动态切换去重规则:

class Test1 {
    public static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
        // 单线程流无需ConcurrentHashMap,用普通HashMap即可
        Map<Object, Boolean> seen = new HashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    private static List<Pojo> getList() {
        return Arrays.asList(
            new Pojo("123", 100, "A"),
            new Pojo("123", 100, "B"),
            new Pojo("123", 100, "C"),
            new Pojo("456", 200, "D")
        );
    }

    public static void main(String[] args) {   
        List<Pojo> ff1 = new ArrayList<>(getList());
        
        // 按name+price去重
        DeduplicationKey selectedKey = DeduplicationKey.NAME_PRICE;
        List<Pojo> f1 = ff1.stream()
                .filter(distinctByKey(selectedKey.getKeyExtractor()))
                .collect(Collectors.toList());
        System.out.println("按name+price去重结果:");
        f1.forEach(p -> System.out.println(p.getName() + ":" + p.getPrice() + ":" + p.getRef1()));

        // 切换为按ref1去重
        selectedKey = DeduplicationKey.REF1;
        List<Pojo> f2 = ff1.stream()
                .filter(distinctByKey(selectedKey.getKeyExtractor()))
                .collect(Collectors.toList());
        System.out.println("\n按ref1去重结果:");
        f2.forEach(p -> System.out.println(p.getName() + ":" + p.getPrice() + ":" + p.getRef1()));
    }
}

方案2:Map存储动态去重规则(支持运行时新增规则)

如果需要在运行时动态添加新的去重维度,用Map管理键提取逻辑更灵活:

public static void main(String[] args) {   
    List<Pojo> ff1 = new ArrayList<>(getList());
    
    // 定义所有可用的去重规则
    Map<String, Function<Pojo, Object>> keyExtractors = new HashMap<>();
    keyExtractors.put("name_price", p -> p.getName() + "_" + p.getPrice());
    keyExtractors.put("ref1", Pojo::getRef1);
    keyExtractors.put("name", Pojo::getName);

    // 从配置/用户输入动态选择规则
    String selectedRule = "name_price";
    Function<Pojo, Object> keyExtractor = keyExtractors.get(selectedRule);
    
    List<Pojo> result = ff1.stream()
            .filter(distinctByKey(keyExtractor))
            .collect(Collectors.toList());
    
    result.forEach(p -> System.out.println(p.getName() + ":" + p.getPrice() + ":" + p.getRef1()));
}

方案3:反射实现任意字段组合(极致灵活)

如果需要支持任意字段组合(比如运行时指定多个字段名),可以用反射动态生成键提取器:

// 生成组合键提取器的工具方法
public static Function<Pojo, Object> createCompositeKeyExtractor(String... fieldNames) {
    return p -> {
        StringBuilder key = new StringBuilder();
        for (String fieldName : fieldNames) {
            try {
                // 调用对应getter方法(假设字段名对应getXXX格式)
                Method method = Pojo.class.getMethod("get" + capitalize(fieldName));
                Object value = method.invoke(p);
                key.append(value).append("_");
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException("提取字段失败:" + fieldName, e);
            }
        }
        return key.toString();
    };
}

// 辅助方法:首字母大写
private static String capitalize(String str) {
    if (str == null || str.isEmpty()) return str;
    return str.substring(0, 1).toUpperCase() + str.substring(1);
}

使用示例:

public static void main(String[] args) {   
    List<Pojo> ff1 = new ArrayList<>(getList());
    
    // 按name和price组合去重
    Function<Pojo, Object> keyExtractor = createCompositeKeyExtractor("name", "price");
    List<Pojo> result1 = ff1.stream()
            .filter(distinctByKey(keyExtractor))
            .collect(Collectors.toList());

    // 切换为按ref1去重
    keyExtractor = createCompositeKeyExtractor("ref1");
    List<Pojo> result2 = ff1.stream()
            .filter(distinctByKey(keyExtractor))
            .collect(Collectors.toList());
}

注意:反射方式性能略低,且需要处理异常,适合对灵活性要求极高的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:54:54