1. 简介

在本篇文章中,我们将深入探讨在 Java 中创建不可变集合(Immutable Set)的不同方式。

不过,在开始之前,我们先明确一下什么是不可变集合,以及为什么我们需要它。

2. 什么是不可变集合?

一般来说,不可变对象是指一旦创建后,其内部状态就不能再被修改的对象。这种特性使得它天生就是线程安全的。同样的道理也适用于不可变集合。

举个例子,假设我们有一个包含若干值的 HashSet 实例。如果我们把它变成不可变集合,就相当于得到了一个“只读”版本的 Set。因此,任何试图修改它的操作都会抛出 UnsupportedOperationException 异常。

那么,我们为什么需要不可变集合呢?

✅ 最常见的使用场景就是在多线程环境下。我们可以放心地在线程之间共享不可变数据,而无需担心同步问题。

⚠️ 但需要注意的是:不可变性只作用于集合本身,而不作用于集合中的元素。也就是说,虽然不能往集合里添加或删除元素,但我们仍然可以修改集合中对象的属性(前提是这些对象本身是可变的)。

3. 使用核心 Java 创建不可变集合

如果我们只使用 Java 标准库提供的类,可以通过 Collections.unmodifiableSet() 方法来包装原始 Set,从而得到一个不可变集合。

首先,创建一个简单的 HashSet 并初始化一些字符串值:

Set<String> set = new HashSet<>();
set.add("Canada");
set.add("USA");

接着,用 Collections.unmodifiableSet() 包装这个 Set:

Set<String> unmodifiableSet = Collections.unmodifiableSet(set);

最后,为了验证我们的 unmodifiableSet 是真正的不可变集合,我们可以写一个简单的测试用例:

@Test(expected = UnsupportedOperationException.class)
public void testUnmodifiableSet() {
    // 创建并初始化 Set 实例
    Set<String> set = new HashSet<>();
    set.add("Canada");
    set.add("USA");

    Set<String> unmodifiableSet = Collections.unmodifiableSet(set);
    unmodifiableSet.add("Costa Rica"); // 尝试添加元素,应该抛异常
}

不出所料,测试会成功运行。对 unmodifiableSet 调用 add() 方法时会抛出 UnsupportedOperationException

⚠️ 但是,如果我们在原始的 set 上添加新元素会发生什么呢?

set.add("Costa Rica");

此时,由于 unmodifiableSet 只是对原始 Set 的一种“视图”,它也会反映出这个变化。当我们打印 unmodifiableSet 的内容时:

[Canada, USA, Costa Rica]

可以看到,“Costa Rica” 已经出现在了不可变集合中。这说明 Collections.unmodifiableSet() 并不是深拷贝,只是做了个包装。

4. Java 9 中创建不可变集合的方式

从 Java 9 开始,引入了新的静态工厂方法 Set.of(elements) 来创建不可变集合:

Set<String> immutable = Set.of("Canada", "USA");

这种方式非常简洁直观,并且返回的集合是完全不可变的 —— 不仅不能增删改元素,甚至连原始集合的变化也不会影响它。

5. 使用 Guava 创建不可变集合

另一种常用的创建不可变集合的方法是使用 Google Guava 库中的 ImmutableSet 类。

Guava 的 ImmutableSet 会将原集合的数据复制到一个新的不可变实例中。因此,即使我们后续修改了原始 Set,ImmutableSet 内部的内容也不会受到影响。

和标准 Java 一样,对 ImmutableSet 的任何修改操作都会抛出 UnsupportedOperationException

下面介绍几种常见的创建方式。

5.1. 使用 ImmutableSet.copyOf()

顾名思义,ImmutableSet.copyOf() 会把传入集合的所有元素复制一份,生成一个新的不可变集合:

Set<String> immutable = ImmutableSet.copyOf(set);

即使之后我们向原始 set 添加了新元素:

set.add("Costa Rica");

immutable 集合仍然保持不变:

[Canada, USA]

✅ 这才是真正意义上的“不可变”。

5.2. 使用 ImmutableSet.of()

类似于 Java 9 的 Set.of(),Guava 提供了 ImmutableSet.of() 来直接创建不可变集合:

Set<String> immutable = ImmutableSet.of("Canada", "USA");

如果不传参数,则返回一个空的不可变集合。

📌 注意:这种方式与 Java 9 的 Set.of() 功能类似,但在某些细节上略有不同(如 null 值处理、泛型支持等),可以根据项目需求选择使用。

6. 总结

在这篇文章中,我们详细介绍了 Java 中不可变集合的概念及其重要性,并展示了三种主要的创建方式:

  • ✅ 使用 Collections.unmodifiableSet()(Java 标准库)
  • ✅ 使用 Set.of()(Java 9+)
  • ✅ 使用 Guava 的 ImmutableSet

每种方式都有其适用场景:

方式 是否真正不可变 是否支持并发访问 是否依赖第三方库
Collections.unmodifiableSet() ❌(仅包装)
Set.of()
ImmutableSet

最后,本文所有代码示例都可以在 GitHub 上找到。


原始标题:Immutable Set in Java