stream.distinct()依赖equals()和hashcode()判断重复,自定义类需重写二者且逻辑一致;并行流中顺序不保证;无法按字段去重;内存开销大,大数据量慎用。

Stream.distinct() 依赖对象的 equals() 和 hashCode()
它不是按值深比较,而是调用元素的 equals() 和 hashCode() 判断是否重复。如果你传入的是自定义对象(比如 User),但没重写这两个方法,那即使字段内容相同,也会被当作不同元素保留。
- 对
String、Integer等 JDK 内置类型,默认已实现正确逻辑,可直接用 - 对自定义类,必须重写
equals()和hashCode(),且二者逻辑要一致 - 如果只重写
equals()忘了hashCode(),distinct()可能失效或行为不稳定
distinct() 是有状态操作,不能并行流里随意替换顺序
它内部用 LinkedHashSet 缓存已见元素,所以会保留第一次出现的元素,并维持原始顺序。但在并行流中,这个“第一次”取决于线程调度,结果可能不一致。
- 串行流:
Stream.of("a", "b", "a").distinct().toList()总是返回["a", "b"] - 并行流:
Stream.of("a", "b", "a").parallel().distinct().toList()结果不确定,可能是["a", "b"]或["b", "a"] - 若需并行 + 去重 + 稳定顺序,得先
sorted()或改用Collectors.toCollection(LinkedHashSet::new)
distinct() 不适用于按某个字段去重的场景
比如一个 List<user></user> 想按 userId 去重,distinct() 无法直接做到——它只能判断整个对象是否相等,没法指定字段。
- 错误写法:
users.stream().distinct()→ 依赖User.equals(),不是你想要的语义 - 正确思路:用
Collectors.toMap()或Collectors.collectingAndThen()配合TreeSet/LinkedHashMap - 常用替代:
users.stream().collect(Collectors.toMap(User::getId, u -> u, (a, b) -> a)).values()
性能和内存开销比想象中大
distinct() 要缓存所有已见过的元素,最坏情况下(全不重复)内存占用和输入流长度成正比,且每次都要查哈希表。
- 大数据量时(如百万级),容易 OOM 或明显拖慢处理速度
- 如果只是去重后计数,用
stream.distinct().count()不如stream.collect(Collectors.toSet()).size()清晰且可控 - 若上游已排序,可手写跳过相邻重复项(类似归并去重),避免额外集合开销
distinct() 很容易变成隐蔽的 bug 来源。











