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

Stream按属性分组取最大值的最优实现及通用Collector提取

嘿,你的问题很有代表性,我来帮你梳理下现有方案的优劣,再搞定那个通用Collector方法的需求~

一、现有方案的优化建议

先说说你目前的两种实现:

  1. 第一种groupingBy+toList+max:这种方式会先把每个type的所有Match都收集到List里,再遍历List找最大值。缺点是没必要存储所有元素,尤其是数据量大的时候,内存占用会更高,效率不如直接在收集过程中维护最大值。
  2. 第二种groupingBy+maxBy:这个比第一种好,因为maxBy是在流式处理时就实时跟踪当前最大值,不用存全量数据。但美中不足的是maxBy返回的是Optional<Match>,后续还要处理空值判断(虽然你的场景里每个type至少有一个元素,ifPresent不会空,但代码略显繁琐)。

给你推荐更简洁高效的方案——用Collectors.toMap,直接在收集阶段完成重复key的合并逻辑:

Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10), new Match("B", 3), new Match("B", 6), new Match("B", 12), new Match("C", 1))
      .collect(Collectors.toMap(
          Match::getType,  // 分组key:type
          Function.identity(),  // value:Match实例本身
          // 合并规则:当key重复时,保留score更大的那个
          (existingMatch, newMatch) -> existingMatch.getScore() >= newMatch.getScore() ? existingMatch : newMatch
      ))
      .values()  // 直接拿到每个type对应最高score的Match集合
      .forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));

这个方案的优势:

  • 不需要嵌套collectingAndThen,代码更直观
  • 直接返回Match实例而非Optional,避免后续空值处理
  • 内存效率更高,因为不会存储同一type的所有元素,只保留当前最大值

二、提取通用的distinctMaxByProperty方法

要实现你想要的通用静态方法,我们可以用泛型来适配不同的实体类、分组键和排序属性。这里提供两个重载版本,满足不同场景:

版本1:基于可比较的属性值(比如你的score是int,实现了Comparable)

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class CollectorUtils {
    /**
     * 按指定分组键分组,保留每组中指定属性值最大的元素
     * @param groupingKeyExtractor 提取分组键的函数
     * @param valueExtractor 提取用于比较的属性值的函数(属性需实现Comparable)
     * @return 收集到的结果List
     */
    public static <T, K, V extends Comparable<? super V>> Collector<T, ?, List<T>> distinctMaxByProperty(
            Function<? super T, K> groupingKeyExtractor,
            Function<? super T, V> valueExtractor) {
        return Collectors.collectingAndThen(
            Collectors.toMap(
                groupingKeyExtractor,
                Function.identity(),
                // 比较属性值,保留较大的那个元素
                (a, b) -> valueExtractor.apply(a).compareTo(valueExtractor.apply(b)) >= 0 ? a : b
            ),
            map -> new ArrayList<>(map.values())
        );
    }
}

使用方式完全符合你的预期:

Stream.of(new Match("A", 1), new Match("A", 2), new Match("A", 4), new Match("A", 10), new Match("B", 3), new Match("B", 6), new Match("B", 12), new Match("C", 1))
      .collect(CollectorUtils.distinctMaxByProperty(Match::getType, Match::getScore))
      .forEach(m -> System.out.println(m.getType() + ": " + m.getScore()));

版本2:支持自定义Comparator(更灵活,比如处理null值或复杂比较逻辑)

如果你的比较逻辑不是简单的属性大小,或者属性可能为null,可以用这个版本:

import java.util.ArrayList;
import java.util.List;
import java.util.Comparator;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class CollectorUtils {
    public static <T, K> Collector<T, ?, List<T>> distinctMaxByProperty(
            Function<? super T, K> groupingKeyExtractor,
            Comparator<? super T> comparator) {
        return Collectors.collectingAndThen(
            Collectors.toMap(
                groupingKeyExtractor,
                Function.identity(),
                // 用自定义Comparator找最大值
                BinaryOperator.maxBy(comparator)
            ),
            map -> new ArrayList<>(map.values())
        );
    }
}

使用时传入Comparator:

.collect(CollectorUtils.distinctMaxByProperty(
    Match::getType,
    Comparator.comparing(Match::getScore)
))

总结

  • 日常场景下,toMap的方案比groupingBy+maxBy更简洁高效
  • 通用方法可以大大提升代码复用性,尤其是在多个地方需要类似分组取最大值的逻辑时

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:12:03