【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)

简介: 【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(不可变集合篇)

为什么要使用不可变集合

不可变集合包括元组和冻结集合,其特点是不能被修改。元组有序、不可变、可包含不同类型元素,不能进行修改、删除操作,可通过索引访问元素。冻结集合是一种无序的集合类型,内部元素不能修改、添加或删除,因此常用于

不可变对象有很多优点

不可变对象有以下优点:

  1. 对于不可信的库调用,不可变对象是安全的;
  2. 在多线程环境下,由于不可变对象不会发生变化,因此不存在竞态条件问题;
  3. 不可变集合不需要考虑变化,因此可以节省时间和,所有不可变的集合都比可变集合有更好的内性,可以作为常量来安全使用。

Java原生的不可变集合

JDK提供的Collections.unmodifiableXXX方法可以将集合包装为不可变形式,但是我们认为这种方法并不理想。

  • 笨重而繁琐:不能适用于所有需要防御性拷贝的情况;
  • 不安全:必须确保没有人能够通过原始集合的引用进行修改,才能得到实际上不可变的集合;
  • 低效:包装成的集合仍然带有可变集合的负担,例如并发修改检查、散列表需要的额外空间等。

如果不需要修改某个集合,或者希望保持集合不变,将其防御性地拷贝到不可变集合是一个很好的实践。

重要提示:Guava不可变集合的实现不支持null值。通过对Google内部的代码库的详细研究,发现只有5%的情况需要在集合中允许null元素,而其余95%的场景都会快速失败处理null值。如果您需要在不可变集合中使用null,请使用JDK中的Collections.unmodifiableXXX方法

Java原生的不可变集合的案例

这段 Java 代码定义了一个名为 LUCKY_NUMBERS 的静态常量 Set(即不可变集合)。

java

复制代码

public static final Set<Integer> LUCKY_NUMBERS;
static {
 Set<Integer> set = new LinkedHashSet<Integer>();
 set.add(4);
 set.add(8);
 set.add(15);
 set.add(16);
 set.add(23);
 set.add(42);
 LUCKY_NUMBERS = Collections.unmodifiableSet(set);
}

使用 static 修饰符来表示这个 Set 常量是与类相关联,而不是与类的实例相关联的。static 代码块是一个静态初始化块,用于在类加载时初始化静态成员变量。在这里,用 LinkedHashSet 创建了一个 Set 对象,并将其设置为不可变集合。LinkedHashSet 可以保 持插入元素的顺序。

这个 Set 常量包含 6 个自然数,即 4,8,15,16,23 和 42。由于 Set 对象被设置为不可变集合,因此无法将新元素添加到集合中,以在代码运行时保持其中元素的不变性。

java

复制代码

public static final Set<Integer> LUCKY_NUMBERS
 = Collections.unmodifiableSet(
 new LinkedHashSet<Integer>(
 Arrays.asList(4, 8, 15, 16, 23, 42)));

Java原生定义了一个名为 LUCKY_NUMBERS 的静态常量 Set(即不可变集合)。这个 Set 常量包含 6 个自然数,即 4,8,15,16,23 和 42。与之前的示例代码不同的是,这里使用了更简单的方式实现了相同的功能——使用 Collections 类的 unmodifiableSet() 方法。在这里,使用 Arrays 类的 asList() 方法将一些整数转换成一个 List,然后再使用 LinkedHashSet 构造函数创建一个 LinkedHashSet。最后,将创建的 LinkedHashSet 转换为不可变 Set,并将其赋值为 LUCKY_NUMBERS 静态常量。由于 Set 对象被设置为不可变集合,因此无法将新元素添加到集合中,以在代码运行时保持其中元素的不变性。

java

复制代码

public static final ImmutableSet<Integer> LUCKY_NUMBERS
 = ImmutableSet.of(4, 8, 15, 16, 23, 42);

Guava定义了一个名为 LUCKY_NUMBERS 的静态常量集合,其元素为 4,8,15,16,23 和 42。与之前的示例不同的是,这里使用了 Guava 提供的 ImmutableSet,其是一个不可变集合。通过调用 of() 静态方法并传入要添加到集合中的元素,会返回一个不可变的 ImmutableSet 集合对象。ImmutableSet 允许我们创建一个在运行时不可更改的集合,从而使多线程代码的编写更加轻松,并且可以更好地保证代码安全性。由于 LUCKY_NUMBERS 集合使用了 ImmutableSet,因此在其对象的生命周期内,它的内容不会被修改,这有助于确保代码的稳定性和正确性。

怎么使用不可变集合

可用的方法来创建不可变集合有:

  1. 使用copyOf方法,例如ImmutableSet.copyOf(set);

java

复制代码

class Foo {
  Set<Bar> bars;
  Foo(Set<Bar> bars) {
    this.bars = ImmutableSet.copyOf(bars); // defensive copy!
  }
}
  1. 使用of方法,例如ImmutableSet.of("a","b","c")或ImmutableMap.of("a",1,"b",2);

java

复制代码

public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
  "red",
  "orange",
  "yellow",
  "green",
  "blue",
  "purple");

此外,对有序不可变集合来说,排序是在构造集合的时候完成的,如:

java

复制代码

ImmutableSortedSet.of("a", "b", "c", "a", "d", "b")

会在构造时就把元素排序为 a, b, c, d。

  1. 使用构建器工具,例如使用ImmutableList.builder()返回一个builder对象,之后可使用该对象来添加或删除元素,并使用它们来创建不可变的集合。

java

复制代码

public static final ImmutableSet<Color> GOOGLE_COLORS =
  ImmutableSet.<Color>builder()
  .addAll(WEBSAFE_COLORS)
  .add(new Color(0, 191, 255))
  .build();

比想象中更智能的copyOf

值得注意的是,ImmutableXXX.copyOf() 方法在安全的情况下会尝试避免进行拷贝。实现细节不详,但通常是非常智能的,例如:

java

复制代码

ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
thingamajig(foobar);
void thingamajig(Collection<String> collection) {
  ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
  ...
}

在这段代码中,调用ImmutableList.copyOf(foobar)会智能地直接返回 foobar.asList(),这是一个常量时间复杂度的 List 视图,其存储的元素是不可变的。

ImmutableXXX.copyOf(ImmutableCollection) 是一种探索性实现,旨在避免对于一些可能的情况进行线性时间复杂度的拷贝操作,例如:

  • 可以在常量时间内使用底层数据结构,但是ImmutableSet.copyOf(ImmutableList)就不能在常量时间内完成。
  • 如使用ImmutableList.copyOf(hugeList.sublist(0,10)),可以避免不必要地持有 hugeList 的引用,从而防止内存泄漏问题的发生。
  • 如果我们想使用 ImmutableSet.copyOf(myImmutableSortedSet) 操作来获取不改变语义的 ImmutableSet,需要注意不同语义对 hashCode() 和 equals 的影响。为避免此类问题,需要显式地拷贝一份新的 ImmutableSet。

为了减少防御性编程风格带来的性能开销,在合适的情况下我们可以尽量避免进行线性拷贝操作。这样可以最大限度地提高代码的性能表现。

asList视图

所有不可变集合都提供 asList() 方法,可以方便地将集合元素以列表形式进行读取。例如,可以使用 sortedSet.asList().get(k) 在 ImmutableSortedSet 中读取第k个最小元素。

注意,asList() 返回的 ImmutableList 视图实现通常是稳定的且开销较小,而非简单地将元素拷贝进列表中。因此,asList() 返回的列表视图通常比一般列表的平均性能更好。此外,在底层集合的支持下,asList() 总是使用高效的 contains() 方法。

可变集合和不可变集合的关系

可变集合接口 JDK 不可变版本
Collection JDK ImmutableCollection
List JDK ImmutableList
Set JDK ImmutableSet
SortedSet/NavigableSet JDK ImmutableSortedSet/
Map JDK ImmutableMap
SortedMap/NavigableMap JDK ImmutableSortedMap/
Multiset Guava ImmutableMultiset
SortedMultiset/NavigableMultiset Guava ImmutableSortedMultiset/
Multimap Guava ImmutableMultimap
ListMultimap Guava ImmutableListMultimap
SetMultimap Guava ImmutableSetMultimap
BiMap Guava ImmutableBiMap
ClassToInstanceMap Guava ImmutableClassToInstanceMap
Table Guava ImmutableTable

不可变的Map

定义了一个名为 ENGLISH_TO_INT 的静态不可变 Map,用于将字符串类型的英文单词转换为对应的整数值。

java

复制代码

public static final Map<String, Integer> ENGLISH_TO_INT;
static {
 Map<String, Integer> map
 = new LinkedHashMap<String, Integer>();
 map.put("four", 4);
 map.put("eight", 8);
 map.put("fifteen", 15);
 map.put("sixteen", 16);
 map.put("twenty-three", 23);
 map.put("forty-two", 42); 
 ENGLISH_TO_INT = Collections.unmodifiableMap(map);
}

使用 LinkedHashMap 存储字符串-整数的映射关系,其中字符串代表英文单词,整数为对应的数字。在静态代码块中,程序使用 Collections.unmodifiableMap() 将这个 Map 转换为不可修改的 Map,并将其赋值给 ENGLISH_TO_INT 常量。这个过程将会确保 ENGLISH_TO_INT 对象在其生命周期内不会被修改,从而防止了可能的错误或安全问题。

java

复制代码

public static final ImmutableMap<String, Integer>
 ENGLISH_TO_INT = ImmutableMap
 .with("four", 4)
 .with("eight", 8)
 .with("fifteen", 15)
 .with("sixteen", 16)
 .with("twenty-three", 23)
 .with("forty-two", 42)
 .build();

这段代码使用Guava定义了一个名为 ENGLISH_TO_INT 的静态不可变 Map,用于将字符串类型的英文单词转换为对应的整数值。这个代码的实现方式是使用 Guava 开源库中的 ImmutableMap 实现。静态常量 ENGLISH_TO_INT 的值是通过使用 with() 方法连续地向 ImmutableMap 对象中添加字符串-整数的映射关系,然后调用 build() 方法建立不可变的 Map 对象实现的。

与第一个代码示例相比,这个实现方式更加简洁且语法更加直观。此外,Guava 中的 ImmutableMap 实现具有更好的性能和可读性,并能够避免并发修改和不必要的内存使用,从而更容易实现代码的可靠性和稳定性。

ImmutableMap.of

java

复制代码

static final ImmutableMap<Integer, String> MAP
 = ImmutableMap.of(1, "one", 2, "two");

这段 Java 代码定义了一个名为 MAP 的静态不可变 Map 对象,它的键是整数类型的 1 和 2,分别对应的值是字符串类型的 "one" 和 "two"。

ImmutableMap.of() 方法是 Guava 开源库中用于创建不可变 Map 对象的快捷方式。这个方法支持传递 1 到 5 个键值对作为参数,从而只需一行代码就能创建一个简单的不可变 Map 对象。由于这个 Map 对象是不可变的,因此它的键值对不能被增加、删除或修改。这种不可变性使得这个 Map 对象更加稳定和安全,并且在多线程环境中更加可靠。

总的来说,这种代码编写风格更加简洁和直观,同时使用了 Guava 提供的不可变集合工具类库,从而可以提高代码的可读性和稳定性。同时这种实现方式还可以避免程序的并发修改和不必要的内存使用,具有更好的性能表现。

目录
打赏
0
1
1
0
379
分享
相关文章
|
23天前
|
《从头开始学java,一天一个知识点》之:控制流程:if-else条件语句实战
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列为你打造Java「速效救心丸」!每天1分钟,地铁通勤、午休间隙即可完成学习。直击高频考点和实际开发中的「坑位」,拒绝冗长概念,每篇都有可运行的代码示例。明日预告:《for与while循环的使用场景》。 ---
55 19
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
随着大模型的越来越盛行,现在很多企业开始接入大模型的接口,今天我从java开发角度来写一个demo的示例,用于接入DeepSeek大模型,国内的大模型有很多的接入渠道,今天主要介绍下阿里云的百炼模型,因为这个模型是免费的,只要注册一个账户,就会免费送百万的token进行学习,今天就从一个简单的可以执行的示例开始进行介绍,希望可以分享给各位正在学习的同学们。
125 3
JAVA接入DeepSeek大模型接口开发---阿里云的百炼模型
JVM实战—1.Java代码的运行原理
本文介绍了Java代码的运行机制、JVM类加载机制、JVM内存区域及其作用、垃圾回收机制,并汇总了一些常见问题。
JVM实战—1.Java代码的运行原理
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
32 2
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
java常见的集合类有哪些
Map接口和Collection接口是所有集合框架的父接口: 1. Collection接口的子接口包括:Set接口和List接口 2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等 3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等 4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
课时146:使用JDT开发Java程序
在 Eclipse 之中提供有 JDT环境可以实现java 程序的开发,下面就通过一些功能进行演示。 项目开发流程
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。