浅谈 Guava 中的 ImmutableMap.of 方法的坑
作者:明明如月学长, CSDN 博客专家,大厂高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。
热门文章推荐:
- (1)《为什么很多人工作 3 年 却只有 1 年经验?》
- (2)《从失望到精通:AI 大模型的掌握与运用技巧》
- (3)《AI 时代,程序员的出路在何方?》
- (4)《如何写出高质量的文章:从战略到战术》
- (5)《我的技术学习方法论》
- (6)《我的性能方法论》
- (7)《AI 时代的学习方式: 和文档对话》
一、背景
Guava 的 ImmutableMap类提供了 of方法,可以很方便地构造不可变 Map。
ImmutableMap<String, Integer> build = ImmutableMap.of("a",1,"b",2);
然而,实际工作开发中很多人会从开始认为非常方便,后面到发现很多大家都会遇到相似的“问题”。
比如 ImmutableMap类的 of 存在很多重载的方法,但是最多只有五个键值对。
有无参的方法:
/*** Returns the empty map. This map behaves and performs comparably to {@link* Collections#emptyMap}, and is preferable mainly for consistency and maintainability of your* code.** <p><b>Performance note:</b> the instance returned is a singleton.*/@SuppressWarnings("unchecked")public static <K, V> ImmutableMap<K, V> of() {return (ImmutableMap<K, V>) RegularImmutableMap.EMPTY;}
有支持一个键值对的方法:
/*** Returns an immutable map containing a single entry. This map behaves and performs comparably to* {@link Collections#singletonMap} but will not accept a null key or value. It is preferable* mainly for consistency and maintainability of your code.*/public static <K, V> ImmutableMap<K, V> of(K k1, V v1) {return ImmutableBiMap.of(k1, v1);}
到支持五个键值对的方法:
/*** Returns an immutable map containing the given entries, in order.** @throws IllegalArgumentException if duplicate keys are provided*/public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3), entryOf(k4, v4), entryOf(k5, v5));}
很多人会遇到的坑:
- 超过五个键值对怎么办?
- key 和 value “居然”都不能为 null?
- 同一个 key 重复 put 报错
二、场景还原
2.1 超过 5 个键值对问题
虽然 of 方法很好用,但是经常会遇到超过 5 个键值对的情况,就非常不方便。
解法1:升级版本
在 guava 31.0 版本以后,已经拓展到了 10 个键值对!
/*** Returns an immutable map containing the given entries, in order.** @throws IllegalArgumentException if duplicate keys are provided* @since 31.0*/public static <K, V> ImmutableMap<K, V> of(K k1,V v1,K k2,V v2,K k3,V v3,K k4,V v4,K k5,V v5,K k6,V v6,K k7,V v7,K k8,V v8,K k9,V v9,K k10,V v10) {return RegularImmutableMap.fromEntries(entryOf(k1, v1),entryOf(k2, v2),entryOf(k3, v3),entryOf(k4, v4),entryOf(k5, v5),entryOf(k6, v6),entryOf(k7, v7),entryOf(k8, v8),entryOf(k9, v9),entryOf(k10, v10));}
解法2:使用 builder 方法
com.google.common.collect.ImmutableMap#builder 方法可以通过构造器的方式不断 put 键值对,最后 build即可,也非常方便。
ImmutableMap<String, Integer> build = ImmutableMap.<String, Integer>builder().put("a", 1).put("b", 2).put("c", 3).put("d",4).put("e",5).put("f",6).build();
也可以参考 2.2 中的解法。
2.2 键值都不允许为 null
复现
很多人看到名字就知道不可“修改” 但不太清楚它的键值都不允许为 null。
key 为空的情况:

value 为空的情况:

真正开发时不会那么简单,有时候需要调用某个接口获取返回值然后再构造一个不可编辑的 Map 返回给下游使用。很可能在测试的时候都没有出现 null 值,发布上线,发现 key 或者 value 为 null,就会造成线上问题 或者 bug。
源码
对于 of的多参数重载:
/*** Returns an immutable map containing the given entries, in order.** @throws IllegalArgumentException if duplicate keys are provided*/public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3) {return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2), entryOf(k3, v3));}
/*** Verifies that {@code key} and {@code value} are non-null, and returns a new immutable entry* with those values.** <p>A call to {@link Entry#setValue} on the returned entry will always throw {@link* UnsupportedOperationException}.*/static <K, V> Entry<K, V> entryOf(K key, V value) {return new ImmutableMapEntry<>(key, value);}
ImmutableMapEntry(K key, V value) {super(key, value);checkEntryNotNull(key, value);}
static void checkEntryNotNull(Object key, Object value) {if (key == null) {throw new NullPointerException("null key in entry: null=" + value);} else if (value == null) {throw new NullPointerException("null value in entry: " + key + "=null");}}
当然,如果你比较心细的话会发现 IDE 中会有警告,也可以很大程度上避免这个问题。
解法
不如换个“殊途同归”的办法,先用 HashMap 去实现同一个 key 的值覆盖的功能,然后通过 Collections.unmodifiableMap来实现不可编辑功能。
Map<String, Integer> map = new HashMap<>();map.put("a", 1);map.put("b", 2);map.put("c", 3);map.put("d", 4);map.put("e", 5);map.put("f", null);Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);System.out.println(unmodifiableMap);

2.3 key 重复报错
复现
如果一不小心 key 重复,也会报 java.lang.IllegalArgumentException异常。
ImmutableMap<String, Integer> build = ImmutableMap.<String, Integer>builder().put("a", 1).put("b", 2).put("c", 3).put("d",4).put("f",5).put("f",6).build();System.out.println(build);

源码
/*** Returns an immutable map containing the given entries, in order.** @throws IllegalArgumentException if duplicate keys are provided*/public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) {return RegularImmutableMap.fromEntries(entryOf(k1, v1), entryOf(k2, v2));}
最底层会对 entry 进行校验:
/*** Checks if the given key already appears in the hash chain starting at {@code keyBucketHead}. If* it does not, then null is returned. If it does, then if {@code throwIfDuplicateKeys} is true an* {@code IllegalArgumentException} is thrown, and otherwise the existing {@link Entry} is* returned.** @throws IllegalArgumentException if another entry in the bucket has the same key and {@code* throwIfDuplicateKeys} is true* @throws BucketOverflowException if this bucket has too many entries, which may indicate a hash* flooding attack*/@CanIgnoreReturnValuestatic <K, V> @Nullable ImmutableMapEntry<K, V> checkNoConflictInKeyBucket(Object key,Object newValue,@CheckForNull ImmutableMapEntry<K, V> keyBucketHead,boolean throwIfDuplicateKeys)throws BucketOverflowException {int bucketSize = 0;for (; keyBucketHead != null; keyBucketHead = keyBucketHead.getNextInKeyBucket()) {if (keyBucketHead.getKey().equals(key)) {if (throwIfDuplicateKeys) {checkNoConflict(/* safe= */ false, "key", keyBucketHead, key + "=" + newValue);} else {return keyBucketHead;}}if (++bucketSize > MAX_HASH_BUCKET_LENGTH) {throw new BucketOverflowException();}}return null;}
最终报错:
static IllegalArgumentException conflictException(String conflictDescription, Object entry1, Object entry2) {return new IllegalArgumentException("Multiple entries with same " + conflictDescription + ": " + entry1 + " and " + entry2);}
解法
ImmutableMap的 builder除了提供 buid 之外, 在 31.0 版本之后还通过了 buildKeepingLast和 buildOrThrow。

可以通过 buildKeepingLast设置当 key 重复时取后面的值。
/*** Returns a newly-created immutable map. The iteration order of the returned map is the order* in which entries were inserted into the builder, unless {@link #orderEntriesByValue} was* called, in which case entries are sorted by value.** <p>Prefer the equivalent method {@link #buildOrThrow()} to make it explicit that the method* will throw an exception if there are duplicate keys. The {@code build()} method will soon be* deprecated.** @throws IllegalArgumentException if duplicate keys were added*/public ImmutableMap<K, V> build() {return buildOrThrow();}/*** Returns a newly-created immutable map, or throws an exception if any key was added more than* once. The iteration order of the returned map is the order in which entries were inserted* into the builder, unless {@link #orderEntriesByValue} was called, in which case entries are* sorted by value.** @throws IllegalArgumentException if duplicate keys were added* @since 31.0*/public ImmutableMap<K, V> buildOrThrow() {return build(true);}/*** Returns a newly-created immutable map, using the last value for any key that was added more* than once. The iteration order of the returned map is the order in which entries were* inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case* entries are sorted by value. If a key was added more than once, it appears in iteration order* based on the first time it was added, again unless {@link #orderEntriesByValue} was called.** <p>In the current implementation, all values associated with a given key are stored in the* {@code Builder} object, even though only one of them will be used in the built map. If there* can be many repeated keys, it may be more space-efficient to use a {@link* java.util.LinkedHashMap LinkedHashMap} and {@link ImmutableMap#copyOf(Map)} rather than* {@code ImmutableMap.Builder}.** @since 31.1*/public ImmutableMap<K, V> buildKeepingLast() {return build(false);}
低版本的话可以考虑先用 HashMap 构造数据,然后使用 com.google.common.collect.ImmutableMap#copyOf(java.util.Map<? extends K,? extends V>) 转换即可。
Map<String, Integer> map = new HashMap<>();map.put("a", 1);map.put("b", 2);map.put("c", 3);map.put("d", 4);map.put("f", 5);map.put("f", 6);ImmutableMap<String, Integer> build = ImmutableMap.copyOf(map);System.out.println(build);
三、为什么?
3.1 为什么默认是 5 个键值对?
其实 31.0 版本,已经支持 10 个键值对了。
此处,斗胆猜测,of方法仅是为了提供更简单的构造 ImmutableMap的方法,而“通常” 5 个就足够了。
然而,实践中很多人发现 5 个并不够,因此高版本中支持 10个键值对。
Guava 也有相关 Issues 的讨论 ImmutableMap::of should accept more entries #2071:
https://github.com/google/guava/issues/2071

3.2 为什么不允许键值为 null ?
Github 上也有相关讨论:
Question: Why RegularImmutableMap.fromEntryArray enforces “not null” policy on values? #5844

wiki 上有相关解释:
https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained
使用 ChatGPT 对上述 wiki 进行关键信息提取:
在谷歌的 Guava 库的设计哲学中,不允许在 ImmutableMap(或其他类似的集合)中使用 null 值有几个关键原因:
防止错误:Guava 团队发现在 Google 的代码库中,大约 95% 的集合不应包含任何 null 值。允许 null 值会增加出错的风险,比如可能导致空指针异常。让这些集合在遇到 null 时快速失败(fail-fast)而不是默默接受 null,对开发者来说更有帮助。
消除歧义:null 值的含义通常不明确。例如,在使用 Map.get(key) 时,如果返回 null,可能是因为映射中该键对应的值为 null,或者该键在映射中不存在。这种歧义会导致理解和使用上的困难。
提倡更清晰的实践:在 Set 或 Map 中使用 null 值通常不是一个好的做法。更清晰的方法是在查找操作中显式处理 null,例如,如果你想在 Map 中使用 null 作为值,最好将那个条目留空,并保持一个单独的非空键集合。这样做可以避免混淆那些映射中键存在但值为 null,和那些映射中根本没有该键的情况。
选择适当的替代方案:如果你确实需要使用 null 值,并且遇到了不友好处理 null 的集合实现时,Guava 建议使用不同的实现。例如,如果 ImmutableList 不满足需求,可以使用 Collections.unmodifiableList(Lists.newArrayList()) 作为替代。
总体而言,Guava 库通过避免在其集合中使用 null,旨在提供更清晰、更健壮、且更易于维护的代码实践。
3.3 为什么重复 key 会报错?
我认为,主要是为了符合“不可变”的语义,既然是不可变,那么相同的 key 不应该重复放入到 map 中。其次,也可以避免意外的数据覆盖或丢失。
四、总结
虽然这个问题并不难,但很多人并不知道会有那么多“坑”,很多人都需要重复思考如何解决这些限制。
因此,本文总结在这里,希望对大家有帮助。

相关文章:
浅谈 Guava 中的 ImmutableMap.of 方法的坑
作者:明明如月学长, CSDN 博客专家,大厂高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。 热门文章推荐&…...
Symbol()和迭代器生成器
目录 1、Symbol() 2、迭代器生成器 执行流程 模拟生成器函数 for of 遍历迭代选择器 yield * Generator函数应用 1、Symbol() Symbol表示独一无二的值 const s1 Symbol(a)const s2 Symbol(a)console.log(s1 s2) // fa…...
USB Type-C的基本原理
1 USB Type-C的基本原理 1.1 基本特性 Figure 1-1 USB Type-C接头外形 USB Type-C(简称USB-C)的基本特性: 1. 接口插座的尺寸与原来的Micro-USB规格一样小,约为8.3mm X 2.5mm 2. 可承受1万次反复插拔 3. 支持正反均可插入的“正反…...
HarmonyOS开发(八):动画及网络
1、动画概述 在ArkUI中,产生动画的方式是改变组件属性值并且指定相关的动画参数。当属性值发生变化后,按照动画参数,从原来的状态过渡到新的状态,就形成一个动画。 动画的相关参数如下: 属性名称 属性类型 默认值 …...
Pinctrl子系统和GPIO子系统
Pinctrl子系统: 借助Princtr子系统来设置一个Pin的复用和电气属性; pinctrl子系统主要做的工作是:1. 获取设备树中的PIN信息;2.根据获取到的pin信息来设置的Pin的复用功能;3.根据获取到的pin信息去设置pin的电气特性…...
Unittest单元测试框架之unittest构建测试套件
构建测试套件 在实际项目中,随着项目进度的开展,测试类会越来越多,可是直到现在我 们还只会一个一个的单独运行测试类,这在实际项目实践中肯定是不可行的,在 unittest中可以通过测试套件来解决该问题。 测试套件&…...
Django回顾4
一.过滤器 1.过滤器格式 {{变量|过滤器名字}} 2.怎么使用 1.注册app 2.在app下创建templatetags模块(模块名只能是templatetags) 3.在包下写一个py文件,随便命名 4.在py文件中写入:from django import template …...
Apache APISIX 体验指南
APISIX 体验指南 所有的 sh 脚本通过 git bash 执行。 出现错误仔细核对文档。 github 地址: 使用 docker 安装 apisix 确保本地安装 Docker 和 Docker-compose 如未安装参开以下文档安装: Docker:https://docs.docker.com/engine/install/c…...
Promise的resolve和reject方法(手写题)
1.resolve 2.reject 3.手写 1.resolve //构造函数上添加 resolve 方法 Promise.resolve function (value) {return new Promise((resolve, reject) > {if (value instanceof Promise) {value.then((val) > {resolve(val)},(err) > {reject(err)})} else {resolve(v…...
关于wiki的Unlink攻击理解--附例题BUUCTF-hitcontraining_bamboobox1
堆机制我研究了很久,一直没有什么很大的进展。堆相较于栈难度大的多。利用手法也多。目前还没有怎么做过堆题。这次就把理解了很久的Unlink写一写。然后找一题实践一下。 在glibc中,堆管理都是用一个个chunk去组织的。这个就不过多阐述。Unlink是glibc一…...
【linux】日志有哪些
Linux系统日志主要有以下几种类型: 内核及系统日志:这种日志数据由系统服务rsyslog统一管理,根据其主配置文件/etc/rsyslog.conf中设置决定内核消息及各种系统程序消息记录到什么位置。/var/log/message:该日志文件存放了内核消息…...
Redis主从复制实现RCE
文章目录 前置知识概念redis常用命令redis module 利用条件利用工具思路例题 [网鼎杯 2020 玄武组]SSRFMe总结 前置知识 概念 背景是多台服务器要保存同一份数据,如何实现其一致性呢?数据的读写操作是否每台服务器都可以处理?这里Redis就提供…...
Flutter应用程序的加固原理
在移动应用开发中,Flutter已经成为一种非常流行的技术选项,可以同时在Android和iOS平台上构建高性能、高质量的移动应用程序。但是,由于其跨平台特性,Flutter应用程序也面临着一些安全风险,例如反编译、代码泄露、数据…...
Centos7部署NFS
搭建NFS存储服务器--基于CentOS7系统 - jianmuzi - 博客园 在CentOS中搭建NFS - 陌上荼靡 - 博客园 NFS简介 NFS 是 Network FileSystem 的缩写,顾名思义就是网络文件存储系统,它最早是由 Sun 公司发展出来的,也是 FreeBSD 支持的文件系统…...
我已经开了一个融资融券的账户了,还可以再在别的券商开两融(信用账户)吗?
融资融券交易又称“证券信用交易”或保证金交易,是指投资者向具有融资融券业务资格的证券公司提供担保物,借入资金买入证券(融资交易)或借入证券并卖出(融券交易)的行为。 简单说就是融资做多,…...
Spring Cloud 版本升级记:OpenFeignClient与Gateway的爱恨交织
Spring Cloud 版本升级记:OpenFeignClient与Gateway的爱恨交织 近日,在负责的项目中,我对 Spring Boot、Spring Cloud 以及 Spring Cloud Alibaba 进行了版本升级。原以为会一切顺利,没想到却遭遇了 Spring Cloud Gateway 无法正…...
华为OD机试 - 最多购买宝石数目(Java JS Python C)
题目描述 橱窗里有一排宝石,不同的宝石对应不同的价格,宝石的价格标记为 gems[i] 0 ≤ i < nn = gems.length宝石可同时出售0个或多个,如果同时出售多个,则要求出售的宝石编号连续; 例如客户最大购买宝石个数为m,购买的宝石编号必须为:gems[i],gems[i+1],...,ge…...
【LeetCode】挑战100天 Day17(热题+面试经典150题)
【LeetCode】挑战100天 Day17(热题面试经典150题) 一、LeetCode介绍二、LeetCode 热题 HOT 100-192.1 题目2.2 题解 三、面试经典 150 题-193.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站,提供各种算法和数据结构的题目&…...
正则表达式的基本语法
1.正则表达式基本语法 两个特殊的符号^和$。他们的作用是分别指出一个字符串的开始和结束。例子如下: "^The":表示所有以"The"开始的字符串("There","The cat"等)࿱…...
使用visual Studio MFC 平台实现对灰度图添加椒盐噪声,并进行均值滤波与中值滤波
平滑处理–滤波 本文使用visual Studio MFC 平台实现对灰度图添加椒盐噪声,并进行均值滤波与中值滤波 关于其他MFC单文档工程可参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-visual Studio MFC 绘制单一颜色三角形、渐变颜色边…...
FastAPI 教程:从入门到实践
FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,支持 Python 3.6。它基于标准 Python 类型提示,易于学习且功能强大。以下是一个完整的 FastAPI 入门教程,涵盖从环境搭建到创建并运行一个简单的…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...
EasyRTC音视频实时通话功能在WebRTC与智能硬件整合中的应用与优势
一、WebRTC与智能硬件整合趋势 随着物联网和实时通信需求的爆发式增长,WebRTC作为开源实时通信技术,为浏览器与移动应用提供免插件的音视频通信能力,在智能硬件领域的融合应用已成必然趋势。智能硬件不再局限于单一功能,对实时…...
