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

news2024/9/22 11:32:28

作者:明明如月学长, 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
   */
  @CanIgnoreReturnValue
  static <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 中。其次,也可以避免意外的数据覆盖或丢失。

四、总结

虽然这个问题并不难,但很多人并不知道会有那么多“坑”,很多人都需要重复思考如何解决这些限制。
因此,本文总结在这里,希望对大家有帮助。


在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1244979.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构与算法编程题16

已知长度为n的线性表A&#xff0c;请写一时间复杂度为O(n)、空间复杂度为O(1)的算法&#xff0c;该算法删除线性表中所有值为item的数据元素。 item 3 数组下标 i 0 1 2 3 4 5 6 7 8 顺序表&#xff1a; 1 2 3 4 3 3 5 3 7 #include <iostream> using namespace std;typ…

jupyter notebook 不知道密码,怎么登录解决办法

jupyter notebook 不知道密码&#xff0c;怎么登录解决办法 1、 windows下&#xff0c;打开命令行&#xff0c;输入jupyter notebook list &#xff1a; C:\Users\tom>jupyter notebook list Currently running servers: http://localhost:8888/?tokenee8bb2c28a89c8a24d…

【【Linux系统下常用指令学习 之 二 】】

Linux系统下常用指令学习 之 二 文件查询和搜索 文件的查询和搜索也是最常用的操作&#xff0c;在嵌入式 Linux 开发中常常需要在 Linux 源码文件中查询某个文件是否存在&#xff0c;或者搜索哪些文件都调用了某个函数等等。 1、命令 find find 命令用于在目录结构中查找文件…

基于IDEA+Tomcat+Mysql开发的企业资产管理

基于Tomcat的企业资产管理信息系统 项目介绍&#x1f481;&#x1f3fb; 企业资产管理系统”项目是一个管理和维护企业资源的平台&#xff0c;能够提供系统管理、内容管理、用户管理、组织架构维护、流程管理、模块管理、角色管理、调度管理和个人设置等功能。 本项目所述的全…

Centos上安装Docker和DockerCompose

安装Docker Docker可以运行在MAC&#xff0c;Windows&#xff0c;CtenOS,UBUNTU等操作系统上。目前主流的版本有Docker CE和Docker EE&#xff0c;CE是免费的开源Docker版本&#xff0c;适用于开发人员和小型团队&#xff0c;EE是适用于企业的容器化解决方案。它基于Docker CE…

【开发实践】使用AES算法加密 .sql文件

一、需求分析 笔者在开发遇到一个小需求&#xff0c;导出数据的sql文件&#xff0c;由于安全监管的需要&#xff0c;对sql文件进行加密处理。实现将sql文件进行加密&#xff0c;最终呈现如下效果&#xff1a; 二、加密文件 1、加密模块 /*** AES加密算法** param sourceFileP…

运动戴什么耳机好?运动无线耳机哪个品牌比较好?运动耳机推荐

​如果你是一名户外运动爱好者&#xff0c;那么一款高品质的运动耳机是必不可少的。它们具备好音质、高稳固性舒适度、防尘防水等多项防护功能&#xff0c;让你在恶劣的天气条件下也能保持音乐的陪伴。面对市面上越来越多的运动耳机&#xff0c;到底哪款更值得入手&#xff1f;…

Co-DETR:DETRs与协同混分配训练论文学习笔记

论文地址&#xff1a;https://arxiv.org/pdf/2211.12860.pdf 代码地址&#xff1a; GitHub - Sense-X/Co-DETR: [ICCV 2023] DETRs with Collaborative Hybrid Assignments Training 摘要 作者提出了一种新的协同混合任务训练方案&#xff0c;即Co-DETR&#xff0c;以从多种标…

自建私有化证书颁发机构(Certificate Authority,CA)实战之 《0x01 Nginx 配置 https单向认证》

自建私有化证书颁发机构&#xff08;Certificate Authority&#xff0c;CA&#xff09;实战之 《0x01 Nginx 配置 https单向认证》 上一篇文章我们介绍了如何自建私有化证书颁发机构&#xff08;Certificate Authority&#xff0c;CA&#xff09;&#xff0c;本篇文章我们将介…

Python 使用XlsxWriter操作Excel

在数据处理和报告生成的领域中&#xff0c;Excel 文件一直是广泛使用的标准格式。为了让 Python 开发者能够轻松创建和修改 Excel 文件&#xff0c;XlsxWriter 库应运而生。XlsxWriter 是一个功能强大的 Python 模块&#xff0c;专门用于生成 Microsoft Excel 2007及以上版本&a…

ECRS生产工时分析软件:工业效率提升的隐形引擎

降本增效往往是企业开工规划的第一步。那到底降什么本&#xff0c;增什么效呢&#xff0c;对于很多企业来说&#xff0c;都是从采购成本入手&#xff0c;结果采购成本是降下来了&#xff0c;但是整体品质却下降了。实际上&#xff0c;要降本增效&#xff0c;优化现场管理才是企…

【LeetCode刷题笔记】DFSBFS(二)

994. 腐烂的橘子(树/图的BFS问题) 解题思路: 多源BFS ,首选找到 所有的腐烂的橘子 ,放入队列中,然后进行 BFS 广搜,广搜的 层数 - 1 就是所需要花费的分钟数。 在最开始先扫描一遍二维数组,将所有的 腐烂的橘子 加入 队列 ,同时统计新鲜橘子的数量 <

Java架构师发展方向和历程

目录 1 导论2 架构师的三观培养3 架构师的遇到的困难4 架构师职责5 架构师之路6 架构师的发展方向7 应用领域架构师8 业务架构师9 系统架构师和企业架构师10 技术路线和演进规划11 一线大厂的技术生态拓张案例12 如何推进项目落地想学习架构师构建流程请跳转:Java架构师系统架…

Spark-06:共享变量

目录 1.广播变量&#xff08;broadcast variables&#xff09; 2.累加器&#xff08;accumulators&#xff09; 在分布式计算中&#xff0c;当在集群的多个节点上并行运行函数时&#xff0c;默认情况下&#xff0c;每个任务都会获得函数中使用到的变量的一个副本。如果变量很…

深信服防火墙路由模式开局部署-手把手教学(小白篇)

PS&#xff1a;深信服的设备只有400能够通过console连接&#xff0c;一般用户是无法连接的&#xff0c;所以大家不要妄想着从Console连接设备了&#xff0c;开局就通过MANAGE进入Web就可以 接通电源后&#xff0c;开机拿一根网线&#xff0c;一端连接防火墙的MANAGE口&#xf…

中国毫米波雷达产业分析2——毫米波雷达产业链分析

一、产业链构成 毫米波雷达产业链分为三部分&#xff1a;上游主要包括射频前端组件&#xff08;MMIC&#xff09;、数字信号处理器&#xff08;DSP/FPGA&#xff09;、高频PCB板、微控制器&#xff08;MCU&#xff09;、天线及控制电路等硬件供应商&#xff1b;中游主体是毫米波…

Electron+VUE3开发简版的编辑器【文件预览】

简版编辑器的功能主要是: 打开对话框,选择文件后台读取文件文件前端展示文件内容。主要技术栈是VUE3、Electron和Nodejs,VUE3做页面交互,Electron提供一个可执行Nodejs的环境以及支撑整个应用的环境,nodeJS负责读取文件内容。 环境配置、安装依赖这些步骤就不再叙述了。 …

【Flink】状态管理

目录 1、状态概述 1.1 无状态算子 1.2 有状态算子 2、状态分类 ​编辑 2.1 算子状态 2.1.1 列表状态&#xff08;ListState&#xff09; 2.1.2 联合列表状态&#xff08;UnionListState&#xff09; 2.1.3 广播状态&#xff08;BroadcastState&#xff09; 2.2 按键分…

案例012:Java+SSM+uniapp基于微信小程序的科创微应用平台设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

ElementUI table+dialog实现一个简单的可编辑的表格

table组件如何实现可编辑呢&#xff1f; 我的需求是把table组件那样的表格&#xff0c;实现它点击可以弹出一个框&#xff0c;然后在这个框里面输入你的东西&#xff0c;然后将他回显回去&#xff0c;当然&#xff0c;输入的有可能是时间啥的。 为什么要弹出弹层不在框上直接…