0

0

Map.merge()的详细介绍(附代码)

不言

不言

发布时间:2019-03-22 17:04:09

|

6358人浏览过

|

来源于segmentfault

转载

本篇文章给大家带来的内容是关于map.merge()的详细介绍(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

今天介绍Map的merge方法,让我们来看看它的强大之处。

在JDK的API中,这样的一个方法它是很特别的,它很新颖,它是值得我们花时间去了解的,同时也推荐你可以运用到实际的项目代码中,对你们应该帮助很大。Map.merge())。这可能是Map中最通用的操作。但它也相当模糊,几乎很少人会去使用它。

背景介绍

merge() 可以解释如下:它将新的值赋值给到key中(如果不存在)或更新具有给定值的现有key(UPSERT)。让我们从最基本的例子开始:计算唯一的单词出现次数。在java8之前的时候,代码非常混乱,实际的实现其实已经失去了本质层面的设计意义。

var map = new HashMap<String, Integer>();
words.forEach(word -> {
    var prev = map.get(word);
    if (prev == null) {
        map.put(word, 1);
    } else {
        map.put(word, prev + 1);
    }
});

按照上述代码的逻辑,假设给定一个输入集合,输出的结果如下;

var words = List.of("Foo", "Bar", "Foo", "Buzz", "Foo", "Buzz", "Fizz", "Fizz");
//...
{Bar=1, Fizz=2, Foo=3, Buzz=2}

改进V1

现在让我们来重构它,主要去掉它的一些判断逻辑;

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.put(word, map.get(word) + 1);
});

这样的改进,是可以满足我们的重构要求。putIfAbsent()的具体用法就不过多描述。putIfAbsent那一行代码是一定需要的,否则,后面的逻辑也就会报错。而在下面代码中,又出现了put、get这一点会很奇怪,让我们再继续的进行改进设计。

改进V2

words.forEach(word -> {
    map.putIfAbsent(word, 0);
    map.computeIfPresent(word, (w, prev) -> prev + 1);
});

computeIfPresent是仅当 word中的的key存在的时候才调用给定的转换。否则它什么都不处理。我们通过将key初始化为零来确保key存在,因此增量始终有效。这样的实现是不是已经足够完美?未必,还有其他的思路可以减少额外的初始化。

words.forEach(word ->
        map.compute(word, (w, prev) -> prev != null ? prev + 1 : 1)
);

compute ()就像是computeIfPresent(),但无论给定key的存在与否如何都会调用它。如果key的值不存在,则prev参数为null。将简单移动if 到隐藏在lambda中的三元表达式也远远没有达到最佳的表现。在我向你展示最终版本之前,让我们看一下稍微简化的默认实现Map.merge()源码分析。

改进V3

merge()源码
default V merge(K key, V value, BiFunction<V, V, V> remappingFunction) {
    V oldValue = get(key);
    V newValue = (oldValue == null) ? value :
               remappingFunction.apply(oldValue, value);
    if (newValue == null) {
        remove(key);
    } else {
        put(key, newValue);
    }
    return newValue;
}

代码片段胜过千言万语。 阅读源码总是能够发现新大陆,merge() 适用于两种情况。如果给定的key不存在,它就变成了put(key, value)。但是,如果key已经存在一些值,我们  remappingFunction 可以选择合并的方式。这个功能是完美契机上面的场景:

  • 只需返回新值即可覆盖旧值: (old, new) -> new
  • 只需返回旧值即可保留旧值: (old, new) -> old
  • 以某种方式合并两者,例如: (old, new) -> old + new
  • 甚至删除旧值: (old, new) -> null

如你所见,它  merge() 是非常通用的。那么,我们的问题该如何使用merge()呢?代码如下:

words.forEach(word ->
        map.merge(word, 1, (prev, one) -> prev + one)
);

你可以按照如下思路理解:如果没有key,那么初始化的value等于1;否则,将1添加到现有值。代码中的 one 是一个常量,因为我们的场景中,默认一直是加1,具体变化可以随意切换。

比格设计
比格设计

比格设计是135编辑器旗下一款一站式、多场景、智能化的在线图片编辑器

下载

场景

想象一下,merge()真的那么好用吗?它的场景可以有什么?

举一个例子。你有一个帐户操作类

class Operation {
    private final String accNo;
    private final BigDecimal amount;
}

以及针对不同帐户的一系列操作:

operations = List.of(
    new Operation("123", new BigDecimal("10")),
    new Operation("456", new BigDecimal("1200")),
    new Operation("123", new BigDecimal("-4")),
    new Operation("123", new BigDecimal("8")),
    new Operation("456", new BigDecimal("800")),
    new Operation("456", new BigDecimal("-1500")),
    new Operation("123", new BigDecimal("2")),
    new Operation("123", new BigDecimal("-6.5")),
    new Operation("456", new BigDecimal("-600"))
);

我们希望为每个帐户计算余额(总运营金额)。假如不用merge(),就变得非常麻烦了:

Map balances = new HashMap<String, BigDecimal>();
operations.forEach(op -> {
    var key = op.getAccNo();
    balances.putIfAbsent(key, BigDecimal.ZERO);
    balances.computeIfPresent(key, (accNo, prev) -> prev.add(op.getAmount()));
});

使用merge之后的代码

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), 
                (soFar, amount) -> soFar.add(amount))
);

再进行优化的逻辑。

operations.forEach(op ->
        balances.merge(op.getAccNo(), op.getAmount(), BigDecimal::add)
);

当然结果是正确的,这样简洁的代码心动吗?对于每个操作,add在给定的amount给定accNo

{ 123 = 9.5,456 = - 100 }

ConcurrentHashMap

当我们再延伸到ConcurrentHashMap来,当 Map.merge的出现,和ConcurrentHashMap的结合那是非常的完美的。这样的搭配场景是对于那些自动执行插入或者更新操作的单线程安全的逻辑。

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的Java教程视频栏目!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
抖漫入口地址合集
抖漫入口地址合集

本专题整合了抖漫入口地址相关合集,阅读专题下面的文章了解更多详细地址。

1

2026.03.17

多环境下的 Nginx 安装、结构与运维实战
多环境下的 Nginx 安装、结构与运维实战

本专题聚焦多环境下Nginx实战,详解开发、测试及生产环境的差异化安装策略与目录结构规划。深入剖析配置模块化设计、灰度发布流程及跨环境同步机制。结合监控告警、故障排查与自动化运维工具,提供全链路管理方案,助力团队构建灵活、高可用的Nginx服务体系,从容应对复杂业务场景挑战。

0

2026.03.17

PS 批量添加图片
PS 批量添加图片

本专题整合了PS批量添加图片教程合集,阅读专题下面的文章了解更多详细操作。

0

2026.03.17

Nginx 基础架构:从安装配置到系统化管理
Nginx 基础架构:从安装配置到系统化管理

本专题深入解析Nginx基础架构,涵盖从源码编译与包管理安装,到核心配置文件优化及虚拟主机部署。进一步探讨日志轮转、性能调优、高可用集群构建及自动化运维策略,助力管理员实现从单一服务搭建到企业级系统化管理的全面升级,确保Web服务高效、稳定运行。

1

2026.03.17

mulerun骡子快跑入口地址汇总
mulerun骡子快跑入口地址汇总

本专题整合了mulerun入口地址合集,阅读专题下面的文章了解更多详细内容。

36

2026.03.17

源码编译安装Nginx详解:模块选择、依赖准备与常见错误排查
源码编译安装Nginx详解:模块选择、依赖准备与常见错误排查

本专题详解Nginx源码编译全流程:从GCC、OpenSSL等依赖准备,到按需定制HTTP/SSL/流媒体模块的configure参数策略。深入剖析“缺少库文件”、“配置选项冲突”及“权限错误”等常见报错,提供精准排查思路与解决方案。助您掌握灵活构建高性能、定制化Nginx的核心技能,满足复杂生产环境需求。

1

2026.03.17

Linux环境安装Nginx全流程:apt、yum与源码编译方式深度实操
Linux环境安装Nginx全流程:apt、yum与源码编译方式深度实操

本专题深度实操Linux下Nginx三大安装方式:apt/yum包管理器快速部署,适合新手与标准化运维;源码编译灵活定制模块,满足高性能与特殊需求场景。内容涵盖环境准备、依赖安装、配置优化及平滑升级策略,对比各方案优劣,助您根据业务场景选择最佳实践,构建稳定高效的Web服务基石。

5

2026.03.17

c++ 字符处理
c++ 字符处理

本专题整合了c++字符处理教程、字符串处理函数相关内容,阅读专题下面的文章了解更多详细内容。

7

2026.03.17

minimax视频生成教程汇总
minimax视频生成教程汇总

本专题整合了minimax生成视频相关教程,阅读下面的文章了解更多详细操作。

6

2026.03.17

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Redis中文开发手册
Redis中文开发手册

共0课时 | 0人学习

SSDB数据库使用手册
SSDB数据库使用手册

共21课时 | 15万人学习

SSDB数据库使用手册
SSDB数据库使用手册

共21课时 | 12.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号