如何实现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
相关产品推荐
相关产品推荐

