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

如何用Java Stream Collectors生成带计数的总和映射

需求实现与代码校验

现有类定义

Result类

class Result {
    String code;
    String description;
    Duration value;
}

Product类

class Product {
    String code;
    String description;
}

Total类

class Total {
    Duration total;
    long count;
}

需求描述

给定List<Result>列表,生成Map<Product, Total>映射:以Result的code和description组成的Product为键,对应的Total存储该分组下所有Result的value总和(通过Duration.plus方法累加),以及该分组内Result的数量。

用户代码校验

你编写的代码如下:

Map <Product, Total> map = results.stream()
                                  .collect(Collectors.groupingBy(p -> new Product(p.getCode(), p.getDescription()),
    Collectors.collectingAndThen(Collectors.toList(), list -> {
        Duration total = list.stream().map(Result::getValue).reduce(Duration.ZERO, Duration::plus);
        long count = list.size();
        return new Total(total, count);
    })));

存在的问题

  1. 核心错误:Product类未重写equals和hashCode方法。groupingBy依赖键的相等性判断,默认的equals是比较对象内存地址,即使两个Product的code和description完全相同,也会被视为不同键,导致分组结果完全错误。
  2. 效率问题:先将分组结果转为List再二次遍历流求和,属于冗余操作,会降低处理效率。
  3. 依赖问题:代码中调用了p.getCode()和p.getDescription(),但你给出的Result类是直接暴露成员变量,若没有对应的getter方法,会编译失败。

正确实现方案

第一步:修复Product类

必须给Product添加构造方法,并基于code和description重写equals和hashCode:

import java.util.Objects;

class Product {
    String code;
    String description;

    public Product(String code, String description) {
        this.code = code;
        this.description = description;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Product product = (Product) o;
        return Objects.equals(code, product.code) && Objects.equals(description, product.description);
    }

    @Override
    public int hashCode() {
        return Objects.hash(code, description);
    }
}

第二步:优化流式收集代码

方案1:Java 12+ 用Collectors.teeing(推荐)

teeing可以同时收集两个结果(总和与数量),再合并为Total,代码简洁高效:

Map<Product, Total> map = results.stream()
    .collect(Collectors.groupingBy(
        result -> new Product(result.code, result.description),
        Collectors.teeing(
            Collectors.reducing(Duration.ZERO, result -> result.value, Duration::plus),
            Collectors.counting(),
            Total::new
        )
    ));

方案2:Java 8 兼容版本

如果使用Java 8,没有teeing,可以用reducing直接合并Total,避免二次流处理:

Map<Product, Total> map = results.stream()
    .collect(Collectors.groupingBy(
        result -> new Product(result.code, result.description),
        Collectors.reducing(
            new Total(Duration.ZERO, 0L),
            result -> new Total(result.value, 1L),
            (total1, total2) -> new Total(total1.total.plus(total2.total), total1.count + total2.count)
        )
    ));

补充说明

  • 若Result类的成员变量是私有访问权限,需要添加对应的getter方法(比如getCode()、getDescription()、getValue()),否则无法直接访问成员变量。
  • Total类建议添加构造方法,方便直接实例化:
class Total {
    Duration total;
    long count;

    public Total(Duration total, long count) {
        this.total = total;
        this.count = count;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 00:47:42