1. 概述

在本篇文章中,我们将介绍如何使用 Guava 来简化 Java 中 Set 的操作。Guava 提供了大量实用的工具方法,让集合操作更加优雅高效。

先来看一个简单的例子:使用 Guava 创建一个 HashSet,无需显式使用 new 关键字:

Set<String> aNewSet = Sets.newHashSet();

✅ 简单粗暴,一行搞定。


2. 集合并集(Union)

使用 Sets.union() 可以轻松获取两个集合的并集:

@Test
public void whenCalculatingUnionOfSets_thenCorrect() {
    Set<Character> first = ImmutableSet.of('a', 'b', 'c');
    Set<Character> second = ImmutableSet.of('b', 'c', 'd');

    Set<Character> union = Sets.union(first, second);
    assertThat(union, containsInAnyOrder('a', 'b', 'c', 'd'));
}

并集操作会返回两个集合中所有不重复的元素组成的集合。


3. 笛卡尔积(Cartesian Product)

使用 Sets.cartesianProduct() 可以计算两个集合的笛卡尔积:

@Test
public void whenCalculatingCartesianProductOfSets_thenCorrect() {
    Set<Character> first = ImmutableSet.of('a', 'b');
    Set<Character> second = ImmutableSet.of('c', 'd');
    Set<List<Character>> result =
      Sets.cartesianProduct(ImmutableList.of(first, second));

    Function<List<Character>, String> func =
      new Function<List<Character>, String>() {
        public String apply(List<Character> input) {
            return Joiner.on(" ").join(input);
        }
    };
    Iterable<String> joined = Iterables.transform(result, func);
    assertThat(joined, containsInAnyOrder("a c", "a d", "b c", "b d"));
}

⚠️ 注意:笛卡尔积的结果是 Set<List<T>> 类型,为了方便测试,这里使用 FunctionJoiner 将其转换为字符串形式。


4. 集合交集(Intersection)

使用 Sets.intersection() 可以获取两个集合的交集:

@Test
public void whenCalculatingSetIntersection_thenCorrect() {
    Set<Character> first = ImmutableSet.of('a', 'b', 'c');
    Set<Character> second = ImmutableSet.of('b', 'c', 'd');

    Set<Character> intersection = Sets.intersection(first, second);
    assertThat(intersection, containsInAnyOrder('b', 'c'));
}

交集操作返回两个集合中都存在的元素。


5. 对称差集(Symmetric Difference)

对称差集是指两个集合中只存在于其中一个集合的元素:

@Test
public void whenCalculatingSetSymmetricDifference_thenCorrect() {
    Set<Character> first = ImmutableSet.of('a', 'b', 'c');
    Set<Character> second = ImmutableSet.of('b', 'c', 'd');

    Set<Character> intersection = Sets.symmetricDifference(first, second);
    assertThat(intersection, containsInAnyOrder('a', 'd'));
}

也就是说,排除两个集合的交集,剩下的就是对称差集。


6. 幂集(Power Set)

使用 Sets.powerSet() 可以计算一个集合的幂集,即该集合的所有子集(包括空集和自身):

@Test
public void whenCalculatingPowerSet_thenCorrect() {
    Set<Character> chars = ImmutableSet.of('a', 'b');

    Set<Set<Character>> result = Sets.powerSet(chars);

    Set<Character> empty =  ImmutableSet.<Character> builder().build();
    Set<Character> a = ImmutableSet.of('a');
    Set<Character> b = ImmutableSet.of('b');
    Set<Character> aB = ImmutableSet.of('a', 'b');

    assertThat(result, contains(empty, a, b, aB));
}

幂集在组合数学和算法中经常使用,Guava 提供了非常直观的 API 支持。


7. 连续集合(ContiguousSet)

ContiguousSet 是一个有序的、连续的整数集合。例如,表示区间 [10, 30] 的所有整数:

@Test
public void whenCreatingRangeOfIntegersSet_thenCreated() {
    int start = 10;
    int end = 30;
    ContiguousSet<Integer> set = ContiguousSet.create(
      Range.closed(start, end), DiscreteDomain.integers());

    assertEquals(21, set.size());
    assertEquals(10, set.first().intValue());
    assertEquals(30, set.last().intValue());
}

虽然 Java 原生可以用 TreeSet 实现类似功能,但 ContiguousSet 的语义更加清晰,特别适合处理连续整数范围。


8. 范围集合(RangeSet)

RangeSet 用于表示一组不连续但非空的区间,并支持自动合并重叠区间:

@Test
public void whenUsingRangeSet_thenCorrect() {
    RangeSet<Integer> rangeSet = TreeRangeSet.create();
    rangeSet.add(Range.closed(1, 10));
    rangeSet.add(Range.closed(12, 15));

    assertEquals(2, rangeSet.asRanges().size());

    rangeSet.add(Range.closed(10, 12));
    assertTrue(rangeSet.encloses(Range.closed(1, 15)));
    assertEquals(1, rangeSet.asRanges().size());
}

解释一下这个例子:

  • 先添加两个不连续的区间:[1, 10][12, 15]
  • 再添加一个连接它们的区间:[10, 12]
  • 最终 RangeSet 自动合并为一个大区间:[1, 15]

✅ 非常适合处理区间合并、范围判断等场景。


9. 多重集合(MultiSet)

与普通 Set 不同,Multiset 允许重复元素,并记录每个元素出现的次数:

@Test
public void whenInsertDuplicatesInMultiSet_thenInserted() {
    Multiset<String> names = HashMultiset.create();
    names.add("John");
    names.add("Adam", 3);
    names.add("John");

    assertEquals(2, names.count("John"));
    names.remove("John");
    assertEquals(1, names.count("John"));

    assertEquals(3, names.count("Adam"));
    names.remove("Adam", 2);
    assertEquals(1, names.count("Adam"));
}

Multiset 是统计元素频次的利器,比如词频统计、排行榜等场景。


10. 获取 MultiSet 中出现次数最多的 N 个元素

下面是一个更实用的例子:获取 Multiset 中出现次数最多的前 N 个元素:

@Test
public void whenGetTopOcurringElementsWithMultiSet_thenCorrect() {
    Multiset<String> names = HashMultiset.create();
    names.add("John");
    names.add("Adam", 5);
    names.add("Jane");
    names.add("Tom", 2);

    Set<String> sorted = Multisets.copyHighestCountFirst(names).elementSet();
    List<String> sortedAsList = Lists.newArrayList(sorted);
    assertEquals("Adam", sortedAsList.get(0));
    assertEquals("Tom", sortedAsList.get(1));
}

使用 Multisets.copyHighestCountFirst() 可以按出现次数从高到低排序,非常方便。


11. 总结

本文介绍了 Guava 中 Sets 工具类的常用方法和实际应用场景,包括:

  • 并集、交集、差集、对称差集
  • 笛卡尔积与幂集
  • 连续集合与范围集合
  • 多重集合及其排序统计

这些工具极大提升了 Java 集合的操作效率和可读性。

📌 所有示例代码均可在 GitHub 项目 中找到,基于 Eclipse 构建,导入即可运行。


原始标题:Guava - Sets