Collections.disjoint 通过遍历小集合、调用大集合的 contains 方法判断无交集,性能关键在于“小集合遍历+高效contains”,需手动确保小集合为第一参数,并优先使用 HashSet/TreeSet 作被查集合,同时自定义对象必须重写 equals 和 hashCode。

Java 中 Collections.disjoint 判断两个集合是否无交集,核心优化就是“用小的去查大的”——遍历较小集合,对每个元素调用较大集合的 contains 方法。一旦命中,立刻返回 false(存在交集);全都不中,才返回 true(无交集)。这不是玄学,而是可掌控的性能杠杆。
为什么一定要让小集合当“遍历方”
它不构造中间集合,也不全量比对,只做最短路径的探查。假设集合 A 有 10 个元素、集合 B 有 10 万,若遍历 A(10 次 contains),平均耗时远低于遍历 B(10 万次 contains)。尤其当 contains 本身较慢时(比如 ArrayList 是 O(n)),顺序错一次,性能可能差百倍。
- 内部虽有启发式判断,但不透明、不可控,不能依赖
- 手动比较
c1.size()和c2.size(),显式把小的放第一个参数,是最稳妥的做法 - 例如:
Collections.disjoint(smallList, largeHashSet)比反过来更可靠
被查集合必须高效支持 contains
contains 的时间复杂度直接决定整体效率。遍历次数再少,如果每次 contains 都要扫完整个大集合,照样慢。
- 优先选
HashSet或TreeSet作为被查方:HashSet.contains平均 O(1),TreeSet是 O(log n) - 避免用
ArrayList或LinkedList当被查方:它们的contains是 O(n),总耗时可能达 O(小集合大小 × 大集合大小) - 高频场景下,哪怕原始数据是 List,也建议提前转成
new HashSet(originalList)再传入
自定义对象必须重写 equals 和 hashCode
disjoint 依赖 contains,而 contains 又依赖元素的 equals(和 hashCode,尤其对 HashSet)。如果没重写,就走默认引用比较,逻辑必然出错。
立即学习“Java免费学习笔记(深入)”;
- 比如两个
User对象字段完全一样,但没重写equals,disjoint就会认为它们不同 - 若用 HashSet 存储,还必须保证
hashCode与equals逻辑一致,否则contains可能根本找不到已存在的元素 - IDE 通常可一键生成这两个方法,业务上按“什么算相同”来定义,比如 ID 相同即视为同一用户
防御性准备不能省
它不接受 null 集合,也不处理空集合的边界逻辑,这些都得自己兜底。
- 调用前必须判空:
if (c1 == null || c2 == null) throw new IllegalArgumentException(...) - 空集合与任何集合都视为无交集(
Collections.disjoint(Collections.emptyList(), anyList) == true),这点可直接利用,无需额外处理 - 高频循环中反复调用?不如一次性把被查集合转成 HashSet,再手写
for (e : small) { if (largeSet.contains(e)) return false; },更易调试、更可控


















