《Effective Java》解读第46条:优先选择Stream中无副作用的函数

第46条:优先选择Stream中无副作用的函数

在 Java Stream 流水线中,传给中间操作、终端操作的函数,必须是无副作用的------ 函数只做计算 / 映射 / 过滤,不修改外部变量、不读写外部状态、不产生额外行为。

什么是「副作用」?

函数执行时,除了返回一个结果,还偷偷修改了外部世界。

修改外部变量(count++、list.add())

读写文件、网络、数据库

打印日志、控制台输出

修改静态变量、共享对象状态

Stream 设计初衷是声明式、并行安全、可复用、易理解,副作用会彻底破坏这些特性。

例子

反例:

java 复制代码
// 错误示例:有副作用,破坏 Stream 设计
List<String> words = Arrays.asList("apple", "banana", "cat");
int count = 0;

// 错误:lambda 里修改外部 count,产生副作用
words.stream()
     .filter(w -> w.length() > 3)
     .forEach(w -> count++); // 副作用:修改外部变量

System.out.println(count);
  • 修改了外部变量count,当切换为parallelStream()时,会产生并发问题。
  • 副作用在lambda中,难以排查和维护。
  • forEach 应该只应用于最终消费(如打印、存库),不应用于计算。

正例:

java 复制代码
// 正确:无副作用,纯声明式计算
List<String> words = Arrays.asList("apple", "banana", "cat");
long count = words.stream()
                  .filter(w -> w.length() > 3)
                  .count(); // 终端操作直接返回结果,无外部修改

只做输入->输出,不触碰外部变量。

经典使用例子:

java 复制代码
// ❌ 错误(副作用:收集时修改外部 List)
List<String> result = new ArrayList<>();
words.stream()
     .map(String::toUpperCase)
     .forEach(result::add); // 副作用:修改外部list

// ✅ 正确(无副作用,使用收集器)
List<String> result = words.stream()
                           .map(String::toUpperCase)
                           .collect(Collectors.toList());

✅ 允许的行为(无副作用)

纯计算:map(x -> x*2)

纯判断:filter(x -> x>10)

纯提取:map(Student::getName)

使用标准收集器:collect(toList())、count()、sum()

groupingBy 可以分组 + 下游收集器,实现分组统计

counting () 只能放分组里,不要直接用

求和、平均、计数优先用 stream 原生方法,不要用 collect

groupingBy 有 3 个版本,可指定 Map 类型

partitioningBy 分成 true/false

parallel 用 groupingByConcurrent

❌ 禁止的行为(有副作用)

lambda 里修改外部变量、集合

lambda 里 IO 操作(打印、读写文件)

lambda 里修改对象内部状态

用 forEach 做计算 / 收集

为什么最好选择无副作用方法?

  • 并行安全

    Stream 可以轻松并行,但有副作用的函数在并行下一定会出错(竞态条件、数据丢失)。无副作用函数天然线程安全。

  • 可读性强

    声明式代码:我要什么,而不是我要怎么做。别人一眼看懂:过滤→映射→收集,没有隐藏逻辑。

  • 可优化、可复用

    JVM 可以对无副作用的 Stream 做延迟执行、短路优化、合并操作。有副作用会让所有优化失效。

  • 无时序依赖

相关推荐
如何原谅奋力过但无声2 分钟前
现代化Python工具:uv、Ruff、Pyright、Rich(只讲重点版)
vscode·python·uv
Logintern093 分钟前
DeepAgents 主-子 Agent 的底层实现逻辑完全基于 LangGraph 的图机制
python
豆沙沙包?7 分钟前
C++-程序的内存模型(P84-P88)
java·jvm·c++
吴声子夜歌25 分钟前
正则指引——匹配原理
java·正则表达式
码云骑士1 小时前
97-Milvus从零到生产-Standalone-vs-Cluster-索引选择-分片监控扩容
python·milvus
accept 99%1 小时前
python版提取 PDF 的常用第三方库与工具
开发语言·python·pdf
lzhdim1 小时前
C# 通过 Windows API 实现进程内存读写操作
开发语言·windows·c#
未秃头的程序猿1 小时前
凌晨3点被叫醒:线上OOM,我用这套流程40分钟定位根因
java·jvm·后端
小猴子爱上树1 小时前
跨境图片翻译工具,批量处理商品图视频字幕
python·音视频
用户298698530141 小时前
HTML 转 Word 指南:新手入门教程
人工智能·后端·python