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

Java流式处理:按Cause名称分组汇总amount并生成Top3统计结果

问题修正:按Cause名称分组统计并生成Top3汇总字符串

需求说明

遍历Cause对象列表,按name字段分组:

  • 统计每个name的出现次数
  • 汇总该组内所有对象的float类型amount值
  • 取出现次数最多的前3组
  • 将每组的名称、出现次数、amount总和格式化为指定字符串返回

当前代码问题

  1. 计算金额总和时,误用Cause::getAmount作为分组键,而非正确的Cause::getName
  2. 重复对sources执行流式分组操作,存在冗余,降低性能
  3. 拆分结果到多个List后通过索引拼接,存在索引越界风险(虽捕获异常但属于不良实现)

原错误代码片段

List<Float> totalAmounts = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting()))
                .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList();

Cause类结构(含Getter/Setter)

public Cause(String name, int time, float amount) {}

示例数据源

new Cause("Name", 34, 15.53F);
new Cause("Name", 636, 2.12F);
new Cause("Name", 2345, 3.14F);
new Cause("Name", 568, 9F);
new Cause("OtherName", 10, 5.55F);
new Cause("OtherName", 5324, 0.27F);
new Cause("OtherName", 1, 6.21F);
new Cause("RealName", 921, 7.05F);
new Cause("RealName", 3899, 11.11F);
new Cause("RealName", 1782, 8.38F);
new Cause("BadName", 234, 1.01F);
new Cause("BadName", 581, 6.31F);

预期分组统计结果

每组对应(出现次数,amount总和):

(4, 29.79F)  // 对应Name
(3, 12.03F)  // 对应OtherName
(3, 26.54F)  // 对应RealName

待修正完整代码

List<Cause> sources = new CopyOnWriteArrayList<>();

private String topThree() {
    StringBuilder result = new StringBuilder();
    Comparator<Map.Entry<String, Long>> entryComparator = Collections.reverseOrder(Map.Entry.comparingByValue());
    Comparator<Map.Entry<Float, Double>> entryComp = Collections.reverseOrder(Map.Entry.comparingByValue());

    List<String> most = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
            .entrySet().stream().sorted(entryComparator).map(Map.Entry::getKey).limit(3).toList();

    List<Long> mostTimes = sources.stream().collect(Collectors.groupingBy(Cause::getName, Collectors.counting()))
            .entrySet().stream().sorted(entryComparator).map(Map.Entry::getValue).limit(3).toList();

    List<Float> total = sources.stream().collect(Collectors.groupingBy(Cause::getAmount, Collectors.counting()))
            .entrySet().stream().sorted(entryComp).map(Map.Entry::getValue).limit(3).toList();

    for (int i = 0; i < most.size(); i++) {
        try {
            result.append((i+1) + ". " + most.get(i) + " " + mostTimes.get(i) + " " + total.get(i) + "\n");
        } catch (ArrayIndexOutOfBoundsException ignored) {}
    }
    return result.toString();
}

@Getter @Setter
private static final class Cause {
    private Source source;
    private String name;
    private float amount;
    private int tickRecordedOn;

    public Cause(Source source, String name, float amount, int tickRecordedOn) {
        this.source = source;
        this.name = name;
        this.amount = amount;
        this.tickRecordedOn = tickRecordedOn;// Used for syncing, nothing else
    } 
}

修正后的代码

List<Cause> sources = new CopyOnWriteArrayList<>();

private String topThree() {
    StringBuilder result = new StringBuilder();
    // 一次性分组,同时统计每个name的出现次数和amount总和
    Map<String, Map.Entry<Long, Float>> groupStats = sources.stream()
            .collect(Collectors.groupingBy(
                    Cause::getName,
                    Collectors.teeing(
                            Collectors.counting(),
                            Collectors.summingDouble(Cause::getAmount),
                            (count, sum) -> new AbstractMap.SimpleEntry<>(count, (float) sum)
                    )
            ));

    // 按出现次数倒序排序,取前3组并拼接结果
    AtomicInteger rank = new AtomicInteger(1);
    groupStats.entrySet().stream()
            .sorted((e1, e2) -> Long.compare(e2.getValue().getKey(), e1.getValue().getKey()))
            .limit(3)
            .forEach(entry -> {
                result.append(String.format("%d. %s %d %.2f%n",
                        rank.getAndIncrement(),
                        entry.getKey(),
                        entry.getValue().getKey(),
                        entry.getValue().getValue()
                ));
            });

    return result.toString().trim();
}

@Getter @Setter
private static final class Cause {
    private Source source;
    private String name;
    private float amount;
    private int tickRecordedOn;

    public Cause(Source source, String name, float amount, int tickRecordedOn) {
        this.source = source;
        this.name = name;
        this.amount = amount;
        this.tickRecordedOn = tickRecordedOn;// Used for syncing, nothing else
    } 
}

修正说明

  1. 消除冗余流式处理:通过Collectors.teeing(Java 12+支持)一次性完成分组、次数统计和金额总和计算,避免多次遍历数据源
  2. 修正分组键错误:统一使用Cause::getName作为分组依据,不再误用getAmount
  3. 简化结果拼接:直接在排序后的流中完成字符串拼接,避免拆分多个List导致的索引越界问题
  4. 格式化输出:使用String.format保证输出格式统一,符合要求的(Cause Name) (出现次数) (amount总和)格式

期望返回格式示例

1. Name 4 29.79
2. OtherName 3 12.03
3. RealName 3 26.54

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 01:37:04