J*a Stream Collectors:高效聚合Map中现有键的值并求和


java stream collectors:高效聚合map中现有键的值并求和

本文深入探讨了如何利用J*a Stream API中的`Collectors.toMap`方法,高效地将数据流转换为Map。核心内容是演示如何在键冲突时,通过自定义合并函数对BigDecimal类型的值进行累加求和,并强调了使用`HashMap::new`作为Map工厂的正确实践,以确保代码的简洁性和封装性,避免外部Map的预先创建。

在J*a开发中,将一个对象集合转换为Map是一种常见操作。尤其当转换过程中可能出现键冲突,并且需要对冲突键的值进行聚合(例如求和)时,J*a Stream API提供了强大而灵活的解决方案。本文将详细介绍如何使用Collectors.toMap结合自定义合并函数和Map工厂,实现对Map中现有键的值进行累加求和。

场景描述

假设我们有一个Position对象的列表,每个Position对象包含assetId、currencyId和value(BigDecimal类型)。我们需要将这些Position对象转换为一个Map,其中PositionKey由assetId和currencyId组合而成。如果不同的Position对象生成了相同的PositionKey,那么它们的value应该被累加到Map中该键对应的BigDecimal值上。

为了清晰地演示,我们首先定义相关的辅助类:

import j*a.math.BigDecimal;
import j*a.util.Objects;
import j*a.util.List;
import j*a.util.ArrayList;
import j*a.util.Map;
import j*a.util.HashMap;
import j*a.util.stream.Collectors;

// 组合键类
class PositionKey {
    String assetId;
    String currencyId;

    public PositionKey(String assetId, String currencyId) {
        this.assetId = assetId;
        this.currencyId = currencyId;
    }

    // 必须重写equals和hashCode,确保Map的键能够正确比较
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PositionKey that = (PositionKey) o;
        return Objects.equals(assetId, that.assetId) &&
               Objects.equals(currencyId, that.currencyId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(assetId, currencyId);
    }

    @Override
    public String toString() {
        return "PositionKey{" +
               "assetId='" + assetId + '\'' +
               ", currencyId='" + currencyId + '\'' +
               '}';
    }
}

// 持仓对象类
class Position {
    String assetId;
    String currencyId;
    BigDecimal value;

    public Position(String assetId, String currencyId, BigDecimal value) {
        this.assetId = assetId;
        this.currencyId = currencyId;
        this.value = value;
    }

    public String getAssetId() { return assetId; }
    public String getCurrencyId() { return currencyId; }
    public BigDecimal getValue() { return value; }

    @Override
    public String toString() {
        return "Position{" +
               "assetId='" + assetId + '\'' +
               ", currencyId='" + currencyId + '\'' +
               ", value=" + value +
               '}';
    }
}

使用Collectors.toMap进行聚合

Collectors.toMap方法有多个重载形式,其中一个非常适用于我们当前场景的是: public static > Collector toMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper, BinaryOperator mergeFunction, Supplier mapFactory)

  • keyMapper: 用于从流元素中提取键的函数。
  • valueMapper: 用于从流元素中提取值的函数。
  • mergeFunction: 当两个流元素映射到相同的键时,用于解决值冲突的函数。
  • mapFactory: 一个提供新的空Map实例的工厂函数(Supplier)。

初始尝试与改进空间

在一些初次尝试中,开发者可能会先创建一个空的HashMap,然后将其作为mapFactory传递给Collectors.toMap,如下所示:

public class PositionAggregator {

    // 模拟获取持仓数据的方法
    private List<Position> getPositions(Long portfolioId) {
        List<Position> positions = new ArrayList<>();
        positions.add(new Position("AAPL", "USD", new BigDecimal("100.50")));
        positions.add(new Position("GOOG", "USD", new BigDecimal("200.00")));
        positions.add(new Position("AAPL", "USD", new BigDecimal("50.25"))); // 相同键,需要累加
        positions.add(new Position("MSFT", "EUR", new BigDecimal("120.75")));
        positions.add(new Position("GOOG", "USD", new BigDecimal("75.00"))); // 相同键,需要累加
        return positions;
    }

    public Map<PositionKey, BigDecimal> getAggregatedMap(final Long portfolioId) {
        final Map<PositionKey, BigDecimal> map = new HashMap<>(); // 预先创建Map

        return getPositions(portfolioId).stream()
            .collect(
                Collectors.toMap(
                    position -> new PositionKey(position.getAssetId(), position.getCurrencyId()), // keyMapper
                    Position::getValue, // valueMapper
                    (oldValue, newValue) -> oldValue.add(newValue), // mergeFunction
                    () -> map // mapFactory,引用外部已创建的Map
                ));
    }
}

虽然上述代码能够实现功能,但() -> map这种形式将一个外部已创建的Map实例传递给Collectors.toMap作为工厂,这在语义上略显不当。mapFactory的目的是提供一个 新的 空Map实例,供收集器内部使用。直接引用外部Map虽然在此特定情况下可能不会导致错误(因为toMap会清空它或直接使用它),但最佳实践是让收集器完全负责Map的创建。

最佳实践:使用HashMap::new作为Map工厂

为了遵循Stream API的函数式编程范式并提高代码的清晰度,我们应该提供一个真正能够创建新HashMap实例的Supplier。最简洁和推荐的方式是使用方法引用HashMap::new。

public class PositionAggregator {

    // 模拟获取持仓数据的方法
    private List<Position> getPositions(Long portfolioId) {
        List<Position> positions = new ArrayList<>();
        positions.add(new Position("AAPL", "USD", new BigDecimal("100.50")));
        positions.add(new Position("GOOG", "USD", new BigDecimal("200.00")));
        positions.add(new Position("AAPL", "USD", new BigDecimal("50.25"))); // 相同键,需要累加
        positions.add(new Position("MSFT", "EUR", new BigDecimal("120.75")));
        positions.add(new Position("GOOG", "USD", new BigDecimal("75.00"))); // 相同键,需要累加
        return positions;
    }

    /**
     * 使用J*a Stream和Collectors.toMap高效聚合持仓数据。
     * 当PositionKey冲突时,对BigDecimal值进行累加。
     *
     * @param portfolioId 投资组合ID
     * @return 聚合后的Map<PositionKey, BigDecimal>
     */
    public Map<PositionKey, BigDecimal> getAggregatedPositionsMap(final Long portfolioId) {
        return getPositions(portfolioId).stream()
            .collect(
                Collectors.toMap(
                    position -> new PositionKey(position.getAssetId(), position.getCurrencyId()), // 键映射函数
                    Position::getValue, // 值映射函数
                    (oldValue, newValue) -> oldValue.add(newValue), // 合并函数:BigDecimal累加
                    HashMap::new // Map工厂:提供新的HashMap实例
                ));
    }

    public static void main(String[] args) {
        PositionAggregator aggregator = new PositionAggregator();
        Map<PositionKey, BigDecimal> aggregatedMap = aggregator.getAggregatedPositionsMap(123L);

        System.out.println("聚合后的持仓Map:");
        aggregatedMap.forEach((key, value) -> System.out.println(key + " -> " + value));

        // 预期输出示例:
        // PositionKey{assetId='AAPL', currencyId='USD'} -> 150.75
        // PositionKey{assetId='GOOG', currencyId='USD'} -> 275.00
        // PositionKey{assetId='MSFT', currencyId='EUR'} -> 120.75
    }
}

合并函数详解:BinaryOperator mergeFunction

在上述代码中,mergeFunction被定义为 (oldValue, newValue) -> oldValue.add(newValue)。这个Lambda表达式的含义是:

  • 当Collectors.toMap尝试将一个新值放入Map,但发现该键已经存在时,它会调用此合并函数。
  • oldValue是Map中该键当前存储的值。
  • newValue是当前流元素映射到的新值。
  • 函数返回的结果将替换Map中该键的旧值。

对于BigDecimal类型,我们必须使用其add()方法进行数值相加,而不是使用+运算符(因为BigDecimal是对象,+运算符不适用于它)。原始问题中的oldValue != null ? oldValue.add(newValue) : newValue是一个更健壮的写法,它考虑了oldValue可能为null的情况。然而,在大多数实际应用中,如果valueMapper(Position::getValue)始终返回非null的BigDecimal,并且Map中初始值也是非null的,那么oldValue就不会是null,此时直接使用oldValue.add(newValue)是安全且简洁的。如果你的数据源确实可能产生null的BigDecimal值,那么加上null检查会更安全。

注意事项与总结

  1. equals()和hashCode()的正确实现:作为Map的键,PositionKey类必须正确重写equals()和hashCode()方法。这是Map正确识别和处理键冲突的基础。
  2. BigDecimal的精确计算:使用BigDecimal进行金融计算是最佳实践,因为它提供了精确的浮点数运算,避免了double或float带来的精度问题。务必使用其提供的add()、subtract()等方法进行运算。
  3. mapFactory的选择:HashMap::new是创建默认HashMap的简洁方式。如果你需要一个特定类型的Map(例如TreeMap用于排序键,或ConcurrentHashMap用于并发环境),你可以提供相应的Supplier,例如TreeMap::new。
  4. 流式操作的优势:使用Stream API可以使数据处理逻辑更加声明式、简洁和易读。它将“做什么”与“如何做”分离,提高了代码的可维护性。

通过以上方法,我们可以利用J*a Stream API强大而灵活的Collectors.toMap,以一种优雅且高效的方式,将数据流转换为Map,并根据业务需求对冲突键的值进行聚合。这种模式在处理各种数据转换和汇总任务时都非常有用。

以上就是J*a Stream Collectors:高效聚合Map中现有键的值并求和的详细内容,更多请关注其它相关文章!


# 提供一个  # 无锡本地建设网站  # 定制网站建设服务公司  # 网站SEO优化工程师招聘  # 柳州附近网站建设营销  # 网站推广图  # 温州seo流量  # 泰州产品网站推广哪家好  # 教育行业的营销推广方式  # 品牌全网营销推广平台  # SEO入门单反推荐  # 是一个  # 的是  # 大而  # 配置文件  # java  # 重写  # 自定义  # 运算符  # 转换为  # 持仓  # gate  # 封装性  # java开发  # 金融  # stream  # ai  # app  # go 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: VS Code源代码管理(SCM)视图的进阶使用技巧  cad怎么隐藏指定的图层_cad隐藏或冻结图层方法  《360浏览器》设置摄像头权限方法  《小宇宙》标记不友善评论方法  Sublime怎么格式化HTML代码_Sublime前端代码美化插件使用指南  J*aScript与CSS动画:实现平滑顺序淡入淡出效果并解决显示冲突  《东方财富》条件单关闭方法  动漫岛汉化官网网 动漫岛官方动漫汉化地址  优化2xN网格最大路径和的动态规划算法实践  Flexbox布局中Stencil组件宽度不显示问题解析与:host尺寸控制  PyEZ 配置提交中 RpcTimeoutError 的健壮性处理策略  胃动力不足?试试这5个调理方法  J*aScript文本高亮功能优化:解决多词匹配错误与精确分割策略  抖音号升级成企业资质怎么弄?有什么好处?  VBA Outlook邮件自动化:高效集成Excel数据与列标题的策略  怎样设置开机后自动运行某个程序_Windows启动文件夹与任务计划【自动化】  优化长HTML属性值:SonarQube警告与实用策略  excel怎么计算平均值 excel平均函数*ERAGE使用教学  花生壳内网映射新方案  植物大战僵尸95版游戏版下载_植物大战僵尸95版游戏版安装指南  《花瓣》创建专辑方法  高效调试PHP大型嵌套数组:JSON序列化与可视化工具实践  sublime如何配置PHP开发环境_在sublime中运行与调试PHP代码  小红书网页版首页入口 小红书网页版电脑端官方登录链接  暴风影音官网正式版_暴风影音手机版官网下载安卓  漫蛙官网(首页入口)_漫蛙漫画稳定访问教程分享  如何自定义苹果手机铃声  感染了幽门螺杆菌一定会导致胃癌吗?蚂蚁庄园今日答案最新11.30  iQOO手机信号差网络不稳定怎么办 信号问题原因排查与增强设置【攻略】  rabbitmq 持久化有什么缺点?  抄漫画官网防走失地址_抄漫画最新漫画完整版阅读入口  创建您的便携版VS Code:让配置随身携带  韩剧圈正版官网入口_韩剧圈官方指定登录  微信步数怎么刷_微信步数快速提升技巧  抖音火山版注销账号抖音会注销吗 抖音火山版与抖音账号注销关系  如何使用CSS Grid实现“大方块左侧,小方块右侧垂直堆叠”的水平布局  J*aScript大数运算_BigInt使用指南  《爱笔思画x》魔棒工具抠图教程  126手机126邮箱登录_126邮箱手机登录入口官网  QQ网页版入口导航 QQ网页版在线访问通道  C++ cast类型转换总结_C++ reinterpret_cast与const_cast的使用  解决CSS布局中意外顶部空白问题的教程  在Django中动态检查模型关联:一种灵活的解决方案  《百果园》充值余额方法  mysql中外键约束如何使用_mysql FOREIGN KEY操作  《sketchbook》选中部分图案移动方法  使用Python和NLTK从文本中高效提取名词的实用教程  解决J*aScript动态图片上传中ID重复问题:在同一页面显示多张独立图片  b站如何管理订阅_b站订阅标签分类管理  微信朋友圈怎么设置三天可见 微信朋友圈设置指定天数可见步骤【教程】 

 2025-12-02

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.