Stream本质是不修改原集合、可链式调用、分中间与终端操作的“数据流水线”;常见创建方式包括集合.stream()、Arrays.stream()、Stream.of()、Files.lines()等;中间操作如filter/map/flatMap返回新Stream,终端操作如collect/forEach/count触发执行且仅能有一个。

Java Stream 百问百答教程,不是要你背一百个问题,而是抓住几个关键脉络,把 Stream 用得清楚、用得稳、用得对。
Stream 的本质是“数据流水线”:不改原集合、操作可链式、执行分两段(中间操作 + 终端操作),理解这点,90% 的困惑就消了一半。
Stream 创建的常见方式有哪些?
- 集合调用
.stream():最常用,比如list.stream()、set.stream() - 数组用
Arrays.stream():支持基本类型数组(如int[]→IntStream)和引用类型数组 -
Stream.of():适合少量已知元素,如Stream.of("a", "b", "c") - 文件行流:
Files.lines(Paths.get("file.txt")),记得 try-with-resources 包裹 - 生成式流:
Stream.generate(() -> "x").limit(5)或Stream.iterate(1, n -> n + 1).limit(10) - 数值范围:
IntStream.range(1, 10)(不含 10)、IntStream.rangeClosed(1, 10)(含 10)
注意:创建后默认是串行流;加 .parallel() 可转并行,但并非总更快——小数据或简单操作反而可能变慢。
哪些操作是中间操作?哪些是终端操作?
中间操作(返回新 Stream,可链式调用,不触发执行):
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
filter():按条件筛数据 -
map():一对一转换,如字符串转大写、对象转 ID -
flatMap():一对多展开,比如把List<List<Integer>>拍平成Stream<Integer> -
sorted()、distinct()、limit()、skip()、peek()(调试用)
终端操作(触发整个流水线执行,只能有一个,且执行后 Stream 就失效):
-
collect():最常用,转成List/Set/Map/String等,如collect(Collectors.toList()) -
forEach():遍历,适合副作用操作(如打印、发消息),但不保证顺序(并行流下) -
count()、anyMatch()、allMatch()、noneMatch() -
findFirst()、findAny():返回Optional,安全取值 -
reduce():聚合计算,如求和、拼接、自定义合并逻辑
⚠️ 错误示范:stream.filter(...).map(...).forEach(...).filter(...) —— forEach 是终端操作,后面再链 filter 会编译失败。
怎么避免常见坑?
-
Stream 只能消费一次:
stream.collect(...)之后再stream.count()会抛IllegalStateException -
修改原集合风险:虽然 Stream 不改源集合,但如果在
filter或map中修改了集合里的对象状态(比如user.setName("xxx")),源数据仍会被动改变 -
并行流不是万能药:无状态、无依赖、计算量大的操作才适合并行;含
forEach(非线程安全)、collect(需线程安全 collector)时要格外小心 -
空指针别漏防:
map(x -> x.getName().toUpperCase())遇到null直接 NPE,建议先filter(Objects::nonNull)或用Optional包装 -
I/O 流要关闭:
Files.lines()返回的 Stream 实现了AutoCloseable,务必用 try-with-resources:
try (Stream<String> lines = Files.lines(path)) {
lines.filter(...).collect(...);
}什么时候该用 Stream?什么时候别硬套?
适合用:
- 数据筛选、转换、聚合逻辑清晰(如“找出北京用户中年龄大于25的姓名列表”)
- 代码可读性优先,且集合大小适中(几千到几万条没问题)
- 替代传统 for 循环嵌套,尤其是多层 if + list.add 组合
慎用或不用:
- 超大数据量(百万级)且内存敏感 → 考虑分页、流式处理(如数据库游标)
- 需要精确控制执行顺序或索引位置 → Stream 的
forEach不保序,并行下更不可靠 - 简单单次遍历(如只打印)→
for-each更直观轻量 - 频繁反复计算 → Stream 每次调用都新建流水线,不如提前缓存结果
不复杂,但容易忽略细节。真正用熟,靠的是写几次、错几次、再理一遍。

















