![图片[1]-Java 安全并发编程的终极武器:深入 Guava ImmutableSet](https://share.0f1.top/wwj/site/soft/2025/06/13/20250613160620015.webp)
我们来详细介绍一下 Java 中 ImmutableSet
的概念。
重要前提:ImmutableSet
并非 Java 8 标准库(JDK)的一部分,而是来自 Google 的一个非常流行和强大的开源库——Guava。 不过,它的设计哲学与 Java 8 的函数式编程和流(Stream)API 理念高度契合,因此在现代 Java 开发中被广泛使用。
1. 什么是 ImmutableSet
?
ImmutableSet
(不可变集合)是一个实现了 java.util.Set
接口的集合,但它具有 不可变性(Immutability) 的核心特点。
不可变性意味着:
- 一旦一个
ImmutableSet
对象被创建,你就 不能再向其中添加、删除或修改任何元素。 - 任何尝试修改它的操作(如
add()
,remove()
,clear()
)都会抛出UnsupportedOperationException
异常。
这种特性使得 ImmutableSet
在很多场景下比标准的可变 Set
(如 HashSet
, LinkedHashSet
)更加安全和高效。
2. 为什么要使用 ImmutableSet
?(核心优势)
使用不可变集合带来的好处是显而易见的:
- 线程安全(Thread Safety)
- 由于其内容无法被修改,
ImmutableSet
天然是线程安全的。你可以在多个线程之间自由地共享它,而无需任何额外的同步措施(如synchronized
关键字或锁),这大大简化了并发编程。
- 由于其内容无法被修改,
- 防御性编程(Defensive Programming)
- 当你编写一个方法返回一个集合时,如果返回的是可变集合(如
HashSet
),调用者可能会无意中修改这个集合,从而影响到你类内部的状态,造成难以追踪的 bug。 - 返回
ImmutableSet
可以保证你的内部状态不会被外部代码所破坏,让 API 更加健壮。
- 当你编写一个方法返回一个集合时,如果返回的是可变集合(如
- 性能和内存优化
- 内存占用更低:Guava 对
ImmutableSet
做了很多优化。例如,一个空的ImmutableSet
是一个单例,一个只含单个元素的ImmutableSet
也有专门的、更紧凑的实现。 - 用作
Map
的 Key:由于ImmutableSet
的内容是固定的,它的hashCode()
值在创建时就可以计算并缓存起来。当你把它用作HashMap
或HashSet
的元素时,计算哈希值的速度非常快,因为不需要每次都重新计算。
- 内存占用更低:Guava 对
- 代码意图更清晰
- 当你在代码中看到
ImmutableSet
类型时,你立刻就知道这个集合是只读的,这使得代码的意图更加明确,更容易推理和维护。
- 当你在代码中看到
- 保证元素唯一且非空
ImmutableSet
和所有Set
一样,会自动去除重复元素。- 它不允许
null
元素。任何向ImmutableSet
中添加null
的尝试都会在创建时立即抛出NullPointerException
,这是一种 “fail-fast”(快速失败)的设计,能帮助你尽早发现问题。
3. 如何创建 ImmutableSet
?
Guava 提供了多种非常方便的静态工厂方法来创建 ImmutableSet
。
a. 使用 of()
方法
这是最简单直接的方式,适用于你已经明确知道所有元素的情况。
import com.google.common.collect.ImmutableSet;
public class ImmutableSetOfExample {
public static void main(String[] args) {
// 1. 创建一个空的 ImmutableSet
ImmutableSet<String> emptySet = ImmutableSet.of();
System.out.println("空集合: " + emptySet);
// 2. 创建包含单个元素的 ImmutableSet
ImmutableSet<String> singleElementSet = ImmutableSet.of("alpha");
System.out.println("单元素集合: " + singleElementSet);
// 3. 创建包含多个元素的 ImmutableSet
// 注意:它会保持元素的插入顺序(类似于 LinkedHashSet)
ImmutableSet<String> multiSet = ImmutableSet.of("alpha", "beta", "gamma", "alpha");
System.out.println("多元素集合(自动去重): " + multiSet); // 输出: [alpha, beta, gamma]
}
}
b. 使用 copyOf()
方法
从一个已有的集合或迭代器中创建 ImmutableSet
。
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class ImmutableSetCopyOfExample {
public static void main(String[] args) {
// 1. 从一个 List 创建
List<String> nameList = new ArrayList<>();
nameList.add("Alice");
nameList.add("Bob");
nameList.add("Alice"); // 重复元素
ImmutableSet<String> namesFromList = ImmutableSet.copyOf(nameList);
System.out.println("从 List 创建: " + namesFromList); // 输出: [Alice, Bob]
// 2. 从一个 Set 创建
Set<Integer> numberSet = new HashSet<>();
numberSet.add(100);
numberSet.add(200);
ImmutableSet<Integer> numbersFromSet = ImmutableSet.copyOf(numberSet);
System.out.println("从 Set 创建: " + numbersFromSet); // 输出: [100, 200] (顺序可能不确定,取决于 HashSet)
}
}
c. 使用 builder()
方法
当你需要通过编程逻辑(如循环、条件判断)来构建一个 ImmutableSet
时,Builder
模式是最佳选择。
import com.google.common.collect.ImmutableSet;
public class ImmutableSetBuilderExample {
public static void main(String[] args) {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
// 动态添加元素
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
builder.add("Element " + i);
}
}
// 也可以添加一个数组或另一个集合
String[] otherElements = {"extra1", "extra2"};
builder.add(otherElements);
// 构建最终的 ImmutableSet
ImmutableSet<String> finalSet = builder.build();
System.out.println("使用 Builder 创建: " + finalSet);
// 输出: [Element 0, Element 2, Element 4, extra1, extra2]
}
}
4. ImmutableSet
与 Java 8 Stream API 的结合
Guava 完美地融入了 Java 8 的 Stream
API。你可以非常方便地将一个流(Stream)的计算结果收集到一个 ImmutableSet
中。
Guava 提供了一个专门的 Collector
:ImmutableSet.toImmutableSet()
。
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.stream.Collectors;
public class ImmutableSetStreamExample {
public static void main(String[] args) {
List<String> words = List.of("hello", "world", "java", "hello", "stream");
// 使用 Stream 将 List 转换为小写,并收集到 ImmutableSet 中
ImmutableSet<String> uniqueLowercaseWords = words.stream()
.map(String::toLowerCase)
.collect(ImmutableSet.toImmutableSet()); // Guava 提供的 Collector
System.out.println(uniqueLowercaseWords); // 输出: [hello, world, java, stream]
// 对比 Java 8 自带的 Collectors.toSet()
// 它返回的是一个可变的 Set(通常是 HashSet)
java.util.Set<String> mutableSet = words.stream()
.collect(Collectors.toSet());
// mutableSet.add("new word"); // 这是允许的
// uniqueLowercaseWords.add("new word"); // 这会编译错误或运行时异常
}
}
5. 重要注意事项(Caveat)
ImmutableSet
提供的是“浅不可变性”(Shallow Immutability)。
这意味着:
- 你不能改变
ImmutableSet
中 元素的引用(不能添加或删除元素)。 - 但是,如果集合中的元素本身是 可变对象(比如一个
StringBuilder
或者自定义的Person
对象),你 仍然可以修改那些对象内部的状态。
示例:
import com.google.common.collect.ImmutableSet;
class User {
private String name;
public User(String name) { this.name = name; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } // 可变方法
@Override public String toString() { return "User{name='" + name + "'}"; }
}
public class ShallowImmutabilityExample {
public static void main(String[] args) {
User user1 = new User("Alice");
User user2 = new User("Bob");
ImmutableSet<User> users = ImmutableSet.of(user1, user2);
System.out.println("修改前: " + users);
// users.add(new User("Charlie")); // 这会抛出 UnsupportedOperationException
// 但是我们可以修改集合内元素的状态
user1.setName("Alice Smith");
System.out.println("修改后: " + users);
// 输出:
// 修改前: [User{name='Alice'}, User{name='Bob'}]
// 修改后: [User{name='Alice Smith'}, User{name='Bob'}]
}
}
为了实现真正的深度不可变,你需要确保存入 ImmutableSet
的对象本身也是不可变的。
总结
特性 | 描述 |
---|---|
来源 | Google Guava 库,非 JDK 标准库。 |
核心 | 不可变:创建后无法修改,任何修改操作都会失败。 |
线程安全 | 天然线程安全,无需同步。 |
null | 不允许 null 元素,创建时会快速失败。 |
顺序 | 大多数情况下 保留插入顺序(类似于 LinkedHashSet )。 |
创建方式 | of() , copyOf() , builder() , toImmutableSet() (Stream Collector)。 |
应用场景 | – 公共静态常量 – 作为防御性拷贝返回给调用者 – 多线程共享的数据结构 – Map 的 Key 或 Set 的元素 |
注意 | 提供的是 浅不可变性,需注意元素对象自身是否可变。 |
ImmutableSet
是编写高质量、健壮和高性能 Java 代码的利器,强烈推荐在合适的场景下使用它。要使用它,你需要在你的项目构建工具(如 Maven 或 Gradle)中添加 Guava 依赖。
Maven 依赖示例:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version> <!-- 请使用最新版本 -->
</dependency>