千家信息网

Java中Guava ImmutableMap不可变集合的示例分析

发表于:2026-11-03 作者:千家信息网编辑
千家信息网最后更新 2026年11月03日,这篇文章主要介绍了Java中Guava ImmutableMap不可变集合的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Ja
千家信息网最后更新 2026年11月03日Java中Guava ImmutableMap不可变集合的示例分析

这篇文章主要介绍了Java中Guava ImmutableMap不可变集合的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。

Java Guava不可变集合ImmutableMap的源码分析

一、案例场景

遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样--

public static final  Map dayMap= new HashMap<>();static {    dayMap.put("Monday","今天上英语课");    dayMap.put("Tuesday","今天上语文课");    dayMap.put("Wednesday","今天上数学课");    dayMap.put("Thursday","今天上音乐课");    dayMap.put("Sunday","今天上编程课");    ......}

当时,我就在想,是否可以进一步优化下,使得代码看起来更为优雅些,然后,就发现了Google Guava里的有一个类ImmutableMap,通过这个类可以实现类似建造者模式的链式编程,优化后的效果,如下:

public static final  Map dayMap = ImmutableMap.builder()    .put("Monday","今天上英语课")    .put("Tuesday","今天上语文课")    .put("Wednesday","今天上数学课")    .put("Thursday","今天上音乐课")    .put("Sunday","今天上编程课")    .build();

二、ImmutableMap源码分析

那么,这个ImmutableMap究竟是如何实现这样的功能呢?
在Google Guava官方教程中,Immutable前缀的集合被定义为不可变集合,包括ImmutableSet、 ImmutableMap等,何为不可变集合?就是指,在集合创建后,集合里所有的状态在生命周期内都不可再修改了,只能读。

那么,什么是可再修改的呢,像Jdk中的map、list等,创建后,还可以再通过put()或者add()反复新增或者修改,这种就是可再修改的集合。既然是不可再修改集合,是不是就一定不能再修改了呢?也不是,其实,通过反射还是可以被修改的,但这已经不是不可变集合之所以存在的初衷了。

总结一句话是,不可变集合是线程安全的且可当成常量使用的。

接下来,就进入到ImmutableMap内部,可以看到,其实现了Map接口,跟HashMap有点类似地方在于,Map接口都算是他们的基类,都可以实现父类引用指向子类对象,即向上转型。

public abstract class ImmutableMap implements Map, Serializable {}

这是一个抽象类,若要实现这样调用 ImmutableMap.builder(),表面上就可以猜测到builder()一定是被static定义的静态方法,进到源码里,发现确实如此--

/** * Returns a new builder. The generated builder is equivalent to the builder * created by the {@link Builder} constructor. */public static  Builder builder() {  return new Builder();}

这个方法的定义对于一些初级程序员而言,可能会觉得很奇怪,其实这个方法格式的本质是这样的 --

public  T method(T t)

这是一种泛型的约定规范,第一个定义一种泛型,表示当前方法有一个范型变量类型,用T表示;第二个T是表示method的返回类型为T。

回过头来看这个builder()方法,就很好理解了,是定义一种泛型,表示当前方法的泛型变量,Builder表示返回一个泛型变量为的对象。

前面定义 ImmutableMap.builder(),在这个builder()方法里,就会返回一个new Builder()的对象,这个对象通过构造器,初始化了一个大小为ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY的数组entries,而这个DEFAULT_INITIAL_CAPACITY的默认值是4。

public static class Builder {    Comparator valueComparator;    ImmutableMapEntry[] entries;    int size;    boolean entriesUsed;       public Builder() {      this(ImmutableCollection.Builder.DEFAULT_INITIAL_CAPACITY);    }     Builder(int initialCapacity) {      this.entries = new ImmutableMapEntry[initialCapacity];      this.size = 0;      this.entriesUsed = false;    }    ......}

那么问题来了,这个 ImmutableMapEntry[] 是什么类型的数组呢?

这个ImmutableMapEntry类 ,是继承一个ImmutableEntry类 --

class ImmutableMapEntry extends ImmutableEntry {  static  ImmutableMapEntry[] createEntryArray(int size) {    return new ImmutableMapEntry[size];  }  ImmutableMapEntry(K key, V value) {    super(key, value);    checkEntryNotNull(key, value);  }}

注意一点, checkEntryNotNull(key, value)做了一个校验,这就意味着,存入的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");  }}

在父类ImmutableEntry类里,定义了key和value两个泛型变量,可见,当外部调用builder().put(key,value)来存储key-value数据时,其实是将key-value数据存储到ImmutableEntry对象的key与value里。

class ImmutableEntry extends AbstractMapEntry implements Serializable {  final K key;  final V value;  ......}

提到ImmutableEntry数组来存储key-value数据,就不得不提一下HashMap。

在JDK1.8当中,HashMap是由数组+链表+红黑树组成,它内部的数组是由Node[]定义,而这个 Node 实现的是Map.Entry--

ImmutableMapEntry顶部同样是实现了Entry--

可见,ImmutableMap与HashMap一样,其存储key-value的对象所属的类,都直接或者间接地实现了Entry接口。

分析到这里,再看回Builder类源码,就很容易明白 ,这个ImmutableMapEntry[] entries与HashMap的数组类似,都是用来存储key-value的数据。

接下来,就是分析put的逻辑原理了。

前面分析到的Builder类,其实是属于抽象类 ImmutableMap中的内部静态类,这就意味着,执行ImmutableMap.builder().put("Monday","今天上英语课")的本质,其实是相当于执行了ImmutableMap.new Builder().put("Monday","今天上英语课")。

put方法的源码如下:

public Builder put(K key, V value) {  ensureCapacity(size + 1);   ImmutableMapEntry entry = entryOf(key, value);  // don't inline this: we want to fail atomically if key or value is null  entries[size++] = entry;  return this;}

1、先看第一行代码调用的方法,其作用是判断当新增一个key-value对象存到数组时,是否会有溢出的可能,若出现溢出的情况,就先对数组进行扩容。

private void ensureCapacity(int minCapacity) {  if (minCapacity > entries.length) {    entries =        Arrays.copyOf(            entries, ImmutableCollection.Builder.expandedCapacity(entries.length, minCapacity));    entriesUsed = false;  }}

2、第二行ImmutableMapEntry entry = entryOf(key, value)就是创建一个新的ImmutableMapEntry对象,通过构造器初始化赋值给对象的key与value--

static  ImmutableMapEntry entryOf(K key, V value) {    return new ImmutableMapEntry(key, value);  }

3、第三行代码 entries[size++] = entry是将新增的ImmutableMapEntry对象存储到数组空闲的位置上,这样通过put(key,value)缓存进来的key-value值,就通过对象的形式存入到了数组当中。

4、最后一行,是返回一个this,ImmutableMap能实现链式编程的原因,就是在这个this上。

  • 当理解了这个this,就会理解ImmutableMap设计的精妙之处。

  • 当我们使用链式编程ImmutableMap.builder().put("key1","value1").put("key2","value2") .put("key2","value3")来赋值时,其内部就是反复调用了内部静态类Builder当中的put()方法,那么问题来了,为什么能反复调用呢?

  • 答案就是这个返回的this,其返回的还是Builder对象本身啊,Builderd对象当然可以继续调用其put方法了。在这个反复调用的过程中, 只有entries[size++] 是一直在新增变化的。

  • 这其实是建造者设计模式的一种体现,只不过平常遇到的建造者设计模式,大多都是将对象的各个属性灵活进行拼装,组成一个定制化的对象,而这里,则是灵活去定制化一个数组存储情况。

最后就是,就是执行.build()方法了--

ImmutableMap.builder()    .put("Monday","今天上英语课")    ......    .build();

这个build()源码里写的很复杂,这里直接简单优化了下,大概意思,就是将entries数组包装成一个实现Map接口的子对象进行返回。

public ImmutableMap build() {  switch (size) {    case 0:      return of();    case 1:      return  new SingletonImmutableBiMap(k1, v1);    default:      return  new RegularImmutableMap(entries, table, mask);  }}

当数组长度超过1时,其可以返回SingletonImmutableBiMap或者RegularImmutableMap,两者都是间接实现了Map接口,对比一下各自的类定义--

final class SingletonImmutableBiMap extends ImmutableBiMap {  final transient K singleKey;  final transient V singleValue;  ......}final class RegularImmutableMap extends ImmutableMap {  // entries in insertion order  private final transient Entry[] entries;  // array of linked lists of entries  private final transient ImmutableMapEntry[] table;  // 'and' with an int to get a table index  private final transient int mask;  ......}

发现,都有一个共同特点,类与类中的属性,都是以final修饰符来定义的,这就意味着,一旦调用build()方法创建初始化后,就不可以再改变了。

这就是ImmutableMap集合不可变的真正原因所在。

最后,还有一个问题是,当通过ImmutableMap创建完成一个Map对象后,再试图通过put来插入数据时,会发生什么情况呢?

这时,再通过put方法调用时,例如,以上边定义的dayMap为例,在某个方法里,再试图通过dayMap..put("Monday","今天上英语课") 来修改或者新增map数据时,这里调用的put就已经不是内部类Builder()里的put方法了,而是ImmutableMap本身的put方法,这个方法的源码如下--

/** * Guaranteed to throw an exception and leave the map unmodified. * * @throws UnsupportedOperationException always * @deprecated Unsupported operation. */@CanIgnoreReturnValue@Deprecated@Overridepublic final V put(K k, V v) {  throw new UnsupportedOperationException();}

其注释表示,map unmodified,即无法再被修改,若仍调用put执行,只会喜提一个异常 UnsupportedOperationException。

感谢你能够认真阅读完这篇文章,希望小编分享的"Java中Guava ImmutableMap不可变集合的示例分析"这篇文章对大家有帮助,同时也希望大家多多支持,关注行业资讯频道,更多相关知识等着你来学习!

方法 对象 数组 就是 可变 分析 源码 存储 数据 英语课 英语 接口 编程 变量 篇文章 代码 情况 意味 模式 类型 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 厦门中拓互联网络科技有限公司 互联网与科技发展 科技互联网技术 极限挑战互联网科技 上海亿帐通互联网科技 互联网金融监管科技 镁科技互联网资讯平台 深圳卡尔迅互联网科技有限公司怎么样 阿里巴巴算科技企业还是互联网 互联网科技热点资讯 中国互联网有什么科技含量 央行互联网金融科技公司 受益于互联网科技的高速发展 米聚互联网科技南京有限公司排面 亳州好赞互联网科技有限公司 本届互联网博览会上的黑科技 互联网数据科技专业 锡山区互联网智能科技产品供应 富国互联网科技002692 互联网科技收购价值 西安纸贵互联网科技有限公司 腾讯阿里互联网科技 银优互联网科技有限公司 苏州大垚互联网科技 洛阳昕诺飞互联网科技有限公司 工业互联网等科技实现智能化赋能 科技互联网的核心竞争力 江苏互联网智能科技产品出厂价 科技互联网专业工作会议发言稿 颐高云石互联网科技
0