无状态操作(如filter、map)独立处理各元素,无需依赖历史数据,内存占用小、可惰性执行且易并行;有状态操作(如sorted、distinct、limit)需缓存数据、等待全部输入,内存开销大、延迟执行受限、并行效率低。

Java Stream的中间操作不是随便分组的,关键看它处理元素时“要不要记住前面的数据”。记,就是有状态;不记,就是无状态。这个区别直接影响内存占用、执行时机和并行效果,不是语法风格问题,而是运行机制差异。
怎么一眼判断有没有状态?
只问一个问题:处理当前元素时,需不需要知道之前出现过哪些元素、排在什么位置、值是多少?
-
无状态操作:每个元素独立加工,输入一个,输出一个,完全不查“历史记录”。比如
filter(x -> x > 10),对每个数只比一次;map(s -> s.toUpperCase()),对每个字符串单独转大写。 -
有状态操作:必须攒够信息才能动——
distinct()得把见过的所有值存下来比对;sorted()得等全部数据进来再排序;limit(5)得从头开始数,但不确定第5个在哪,必须按序消费。
常见无状态操作有哪些?
它们轻量、可惰性、易并行,日常用得最多:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- filter:按条件筛掉不符合的元素,适合前置过滤,大幅减少后续计算量。
-
map / mapToInt / flatMap:转换元素类型或结构,比如把对象转ID、把List
- >压平成Stream
。 - peek:调试专用,对每个元素执行动作但不改变流本身,常用来打印中间结果验证惰性执行顺序。
常见有状态操作有哪些?
它们功能强但代价明显,使用时要格外注意位置和数据规模:
- sorted:默认自然排序或按Comparator排序,必须缓存全部元素,空间复杂度O(n),短路操作(如findFirst)在它之后会失效。
- distinct:基于equals+hashCode去重,最坏情况要存所有唯一值,大数据下容易OOM。
-
limit / skip:看似简单,实则依赖顺序索引,
limit(10)必须从第一个开始数满10个,无法跳过前100万条直接取后10条。
实际写代码要注意什么?
不是不能用有状态操作,而是得用得明白、用得克制:
- 把
filter和map尽量放在sorted、distinct前面,比如先.filter(status::isPaid).distinct(),别让去重扛几千条无效数据。 - 避免链式多次有状态操作,像
.sorted().distinct().sorted()不仅冗余,还重复消耗内存和CPU。 - 大数据场景下,优先考虑外部替代方案:用
HashSet预去重、数据库ORDER BY替代sorted()、分页查询代替skip/limit。 - 调试时加
peek(System.out::println),观察打印时机——如果某步之后才输出,说明前面的数据已被暂存,那一步大概率是有状态的。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










