1. Set 理论基础

1.1. 什么是 Set?

Set 是一种无序且不包含重复元素的集合结构。这是它最显著的特点。

我们可以在 Set 中存储任何类型的对象,但通常用于存储具有某种共性的数据。比如一个动物集合、一个车辆集合。

举个简单的例子,我们有两个整数集合:

setA : {1, 2, 3, 4}

setB : {2, 4, 6, 8}

我们可以用韦恩图(Venn Diagram)来表示这两个集合之间的关系:

A Venn Diagram of Two Sets

1.2. 集合的交集(Intersection)

交集是指两个集合中共同存在的元素

从上面的例子中可以看到,2 和 4 同时存在于 setA 和 setB 中,因此它们的交集为:

setA ∩ setB = {2, 4}

用韦恩图表示如下:

A Venn Diagram of Intersection

1.3. 集合的并集(Union)

并集是指两个集合中所有不重复的元素的集合。

合并 setA 和 setB 后,去掉重复的元素,结果为:

setA ∪ setB = {1, 2, 3, 4, 6, 8}

韦恩图如下:

A Venn Diagram of Union

1.4. 集合的相对补集(Relative Complement)

相对补集也叫集合差(Set Difference),是指一个集合中有而另一个集合中没有的元素

我们来看两个例子:

  • setA 中不在 setB 中的元素:{1, 3}
  • setB 中不在 setA 中的元素:{6, 8}

韦恩图如下(表示 setA - setB):

A Venn Diagram of Relative Complement

1.5. 子集与超集(Subset & Superset)

如果一个集合 A 中的所有元素都包含在集合 B 中,则 A 是 B 的子集,B 是 A 的超集。

  • A ∪ B = B(如果 A 是 B 的子集)
  • A ∩ B = A(如果 A 是 B 的子集)

2. Java 中的 Set 操作实现

我们先初始化两个 Set:

private Set<Integer> setA = setOf(1, 2, 3, 4);
private Set<Integer> setB = setOf(2, 4, 6, 8);

private static Set<Integer> setOf(Integer... values) {
    return new HashSet<>(Arrays.asList(values));
}

2.1. 交集(Intersection)

使用 retainAll() 方法获取交集。注意该方法会修改原集合,所以需要先复制一份:

Set<Integer> intersectSet = new HashSet<>(setA);
intersectSet.retainAll(setB);
assertEquals(setOf(2, 4), intersectSet);

小贴士:retainAll 是原地操作,记得复制原集合。

2.2. 并集(Union)

使用 addAll() 方法实现并集:

Set<Integer> unionSet = new HashSet<>(setA);
unionSet.addAll(setB);
assertEquals(setOf(1, 2, 3, 4, 6, 8), unionSet);

⚠️ 注意:add 会自动去重。

2.3. 相对补集(Relative Complement)

使用 removeAll() 方法获取 setA - setB 的差集:

Set<Integer> differenceSet = new HashSet<>(setA);
differenceSet.removeAll(setB);
assertEquals(setOf(1, 3), differenceSet);

⚠️ 也是原地操作,注意备份。


3. 使用 Stream 实现 Set 操作

Java 8 引入的 Stream API 也可以用来操作集合。

3.1. 交集

使用 filter() + contains()

Set<Integer> intersectSet = setA.stream()
    .filter(setB::contains)
    .collect(Collectors.toSet());
assertEquals(setOf(2, 4), intersectSet);

✅ 简洁清晰,推荐使用。

3.2. 并集

使用 Stream.concat() 合并流并去重:

Set<Integer> unionSet = Stream.concat(setA.stream(), setB.stream())
    .collect(Collectors.toSet());
assertEquals(setOf(1, 2, 3, 4, 6, 8), unionSet);

⚠️ 注意顺序可能会被打乱,因为 Set 无序。

3.3. 相对补集

过滤掉 setB 中存在的元素:

Set<Integer> differenceSet = setA.stream()
    .filter(val -> !setB.contains(val))
    .collect(Collectors.toSet());
assertEquals(setOf(1, 3), differenceSet);

✅ 可读性强,适合写业务逻辑。


4. 使用第三方库简化操作

除了原生 API,我们也可以使用一些成熟的工具库,代码更清晰,语义更明确。

4.1. 引入依赖

Maven 配置如下:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.3</version>
</dependency>

4.2. Guava Sets

Guava 提供了静态方法直接操作集合:

Set<Integer> intersectSet = Sets.intersection(setA, setB);
assertEquals(setOf(2, 4), intersectSet);

Set<Integer> unionSet = Sets.union(setA, setB);
assertEquals(setOf(1, 2, 3, 4, 6, 8), unionSet);

✅ 代码语义清晰,推荐用于复杂逻辑。

4.3. Apache Commons SetUtils

Apache Commons 也提供了类似功能:

Set<Integer> intersectSet = SetUtils.intersection(setA, setB);
assertEquals(setOf(2, 4), intersectSet);

Set<Integer> unionSet = SetUtils.union(setA, setB);
assertEquals(setOf(1, 2, 3, 4, 6, 8), unionSet);

✅ 同样语义清晰,适合已有 Apache 依赖的项目。


5. 总结

本文我们从集合的基本概念出发,介绍了交集、并集、补集、子集等集合操作,并通过以下几种方式在 Java 中实现了这些操作:

操作类型 原生实现 Stream 实现 Guava Commons
交集 retainAll filter + contains Sets.intersection SetUtils.intersection
并集 addAll concat + toSet Sets.union SetUtils.union
补集 removeAll filter + not contains 不支持 SetUtils.difference

✅ 推荐:

  • 简单逻辑用 Stream,语义清晰
  • 复杂项目用 Guava 或 Commons,代码更易维护

所有示例代码已上传至 GitHub,地址:Java Set Operations Examples


原始标题:Set Operations in Java