Java 安全并发编程的终极武器:深入 Guava ImmutableSet

图片[1]-Java 安全并发编程的终极武器:深入 Guava ImmutableSet

我们来详细介绍一下 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?(核心优势)

使用不可变集合带来的好处是显而易见的:

  1. 线程安全(Thread Safety)
    • 由于其内容无法被修改,ImmutableSet 天然是线程安全的。你可以在多个线程之间自由地共享它,而无需任何额外的同步措施(如 synchronized 关键字或锁),这大大简化了并发编程。
  2. 防御性编程(Defensive Programming)
    • 当你编写一个方法返回一个集合时,如果返回的是可变集合(如 HashSet),调用者可能会无意中修改这个集合,从而影响到你类内部的状态,造成难以追踪的 bug。
    • 返回 ImmutableSet 可以保证你的内部状态不会被外部代码所破坏,让 API 更加健壮。
  3. 性能和内存优化
    • 内存占用更低:Guava 对 ImmutableSet 做了很多优化。例如,一个空的 ImmutableSet 是一个单例,一个只含单个元素的 ImmutableSet 也有专门的、更紧凑的实现。
    • 用作 Map 的 Key:由于 ImmutableSet 的内容是固定的,它的 hashCode() 值在创建时就可以计算并缓存起来。当你把它用作 HashMapHashSet 的元素时,计算哈希值的速度非常快,因为不需要每次都重新计算。
  4. 代码意图更清晰
    • 当你在代码中看到 ImmutableSet 类型时,你立刻就知道这个集合是只读的,这使得代码的意图更加明确,更容易推理和维护。
  5. 保证元素唯一且非空
    • 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 提供了一个专门的 CollectorImmutableSet.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>
© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享