distinct()基于hashcode()和equals()判断重复,要求二者逻辑一致;未重写时默认按内存地址比较,导致去重失效;它是有状态中间操作,底层用linkedhashset保证顺序与唯一性。

Java Stream 的 distinct() 操作本质是基于 hashCode() 和 equals() 去重,底层使用 LinkedHashSet 保证顺序和唯一性。
distinct 是如何判断重复元素的
它不会直接比较元素内容,而是依赖元素类型的 hashCode() 和 equals() 方法。只有两个对象满足:
- a.hashCode() == b.hashCode(),且
- a.equals(b) == true
才会被认定为重复项,保留第一个出现的元素。
如果自定义类未重写这两个方法,默认使用 Object 的实现(即基于内存地址),会导致所有对象都被视为不重复——即使字段值完全相同。
distinct 的执行时机与中间操作特性
distinct() 是一个**有状态的中间操作**,意味着它必须记住之前见过的元素才能判断是否重复。因此:
- 它不能像
filter()那样做短路优化,必须遍历全部输入(除非配合limit()等操作) - 在并行流中,会为每个线程维护局部去重集合,最后合并结果,可能影响顺序(但最终仍保持原始相对顺序)
- 底层实际使用
LinkedHashSet::add:返回true表示新增成功(即不重复),false表示已存在(跳过)
常见陷阱与正确用法
多数问题源于对象契约未遵守或类型不匹配:
- 字符串、包装类、LocalDate 等 JDK 类已正确实现 equals/hashCode,可直接用 distinct
-
自定义类必须同时重写
equals()和hashCode(),且逻辑一致(例如都基于 id 字段) - 对基本类型数组(如
int[])调用 distinct 无效,因为数组默认没有合理 equals 实现;应改用Arrays.toString(arr)包装或转为 List - 若需按某字段去重(如只看 name),不能直接用 distinct,应改用
Collectors.toMap()或Collectors.collectingAndThen(..., LinkedHashSet::new)等方式实现“字段级去重”
性能与替代方案参考
对于大数据量,distinct() 时间复杂度为 O(n),空间复杂度也为 O(n)(存储已见元素)。若内存敏感或仅需去重后统计数量,可考虑:
- 先
map到关键字段再 distinct(减少对象引用开销) - 用
TreeSet替代(支持排序,但要求元素可比较,且失去插入顺序) - 数据库层去重更高效:如 SQL 中用
SELECT DISTINCT或GROUP BY
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











