1. Set 理论基础
1.1. 什么是 Set?
Set 是一种无序且不包含重复元素的集合结构。这是它最显著的特点。
我们可以在 Set 中存储任何类型的对象,但通常用于存储具有某种共性的数据。比如一个动物集合、一个车辆集合。
举个简单的例子,我们有两个整数集合:
setA : {1, 2, 3, 4}
setB : {2, 4, 6, 8}
我们可以用韦恩图(Venn Diagram)来表示这两个集合之间的关系:
1.2. 集合的交集(Intersection)
交集是指两个集合中共同存在的元素。
从上面的例子中可以看到,2 和 4 同时存在于 setA 和 setB 中,因此它们的交集为:
setA ∩ setB = {2, 4}
用韦恩图表示如下:
1.3. 集合的并集(Union)
并集是指两个集合中所有不重复的元素的集合。
合并 setA 和 setB 后,去掉重复的元素,结果为:
setA ∪ setB = {1, 2, 3, 4, 6, 8}
韦恩图如下:
1.4. 集合的相对补集(Relative Complement)
相对补集也叫集合差(Set Difference),是指一个集合中有而另一个集合中没有的元素。
我们来看两个例子:
- setA 中不在 setB 中的元素:{1, 3}
- setB 中不在 setA 中的元素:{6, 8}
韦恩图如下(表示 setA - setB):
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。