浅谈 Guava 中的 ImmutableMap.of 方法的坑

2023-11-24 06:12

本文主要是介绍浅谈 Guava 中的 ImmutableMap.of 方法的坑,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

作者:明明如月学长, CSDN 博客专家,大厂高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。

热门文章推荐

  • (1)《为什么很多人工作 3 年 却只有 1 年经验?》
  • (2)《从失望到精通:AI 大模型的掌握与运用技巧》
  • (3)《AI 时代,程序员的出路在何方?》
  • (4)《如何写出高质量的文章:从战略到战术》
  • (5)《我的技术学习方法论》
  • (6)《我的性能方法论》
  • (7)《AI 时代的学习方式: 和文档对话》

一、背景

Guava 的 ImmutableMap类提供了 of方法,可以很方便地构造不可变 Map。

 ImmutableMap<Object, Object> 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<Object, Object> build = ImmutableMap.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 为空的情况:
image.png

value 为空的情况:
image.png

真正开发时不会那么简单,有时候需要调用某个接口获取返回值然后再构造一个不可编辑的 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, Object> 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, Object> unmodifiableMap = Collections.unmodifiableMap(map);System.out.println(unmodifiableMap);

在这里插入图片描述

2.3 key 重复报错

复现

如果一不小心 key 重复,也会报 java.lang.IllegalArgumentException异常。

        ImmutableMap<Object, Object> build = ImmutableMap.builder().put("a", 1).put("b", 2).put("c", 3).put("d",4).put("f",5).put("f",6).build();System.out.println(build);

image.png

源码

  /*** 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);}

解法

ImmutableMapbuilder除了提供 buid 之外, 在 31.0 版本之后还通过了 buildKeepingLastbuildOrThrow
image.png
可以通过 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, Object> 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<Object, Object> 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

image.png

3.2 为什么不允许键值为 null ?

Github 上也有相关讨论:
Question: Why RegularImmutableMap.fromEntryArray enforces “not null” policy on values? #5844

image.png

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 方法的坑的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/421372

相关文章

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

浅谈主机加固,六种有效的主机加固方法

在数字化时代,数据的价值不言而喻,但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露,企业的数据安全面临着前所未有的挑战。为了应对这些挑战,一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案,采用先进的安全容器中间件技术,构建起一套内核级的纵深立体防护体系。这一体系突破了传统安全防护的局限,即使在管理员权限被恶意利用的情况下,也能确保服务器的安全稳定运行。 普适主机加固措施:

webm怎么转换成mp4?这几种方法超多人在用!

webm怎么转换成mp4?WebM作为一种新兴的视频编码格式,近年来逐渐进入大众视野,其背后承载着诸多优势,但同时也伴随着不容忽视的局限性,首要挑战在于其兼容性边界,尽管WebM已广泛适应于众多网站与软件平台,但在特定应用环境或老旧设备上,其兼容难题依旧凸显,为用户体验带来不便,再者,WebM格式的非普适性也体现在编辑流程上,由于它并非行业内的通用标准,编辑过程中可能会遭遇格式不兼容的障碍,导致操

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

使用JS/Jquery获得父窗口的几个方法(笔记)

<pre name="code" class="javascript">取父窗口的元素方法:$(selector, window.parent.document);那么你取父窗口的父窗口的元素就可以用:$(selector, window.parent.parent.document);如题: $(selector, window.top.document);//获得顶级窗口里面的元素 $(

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“