累加应优先用sum();若用reduce,identity须为中性值(如0)、accumulator须为纯函数、combiner须满足结合律;并行流必须用三参数reduce;带状态聚合需用不可变容器。

直接用 reduce 做累加看似简单,但初始值配错、参数选错,轻则结果不准,重则并行流下崩溃。关键不在“会不会写”,而在“为什么这么写”。
累加数字:优先用 sum(),非要用 reduce 就守死三条线
对 int 或 long 流,最稳写法是:stream.mapToInt(Integer::intValue).sum() —— 底层优化、线程安全、无需操心初始值。
若坚持用 reduce,必须满足:
-
identity 必须是中性不可变值:如
0(加法)、1L(乘法),绝不能是new AtomicInteger(0)或可变数组 -
accumulator 必须纯函数:写成
(a, b) -> a + b,不修改a;禁用list.add(b)这类副作用操作 -
combiner 必须与 accumulator 行为一致且满足结合律:例如两个部分和合并,也得用
(x, y) -> x + y,不能一个加、一个减
并行流累加:双参数 reduce 是陷阱,三参数才是标配
写 parallelStream().reduce(0, (a,b)->a+b) 在多数情况下能跑通,但这只是“碰巧”——因为加法满足无状态、可结合。一旦换成对象拼接、计数统计或自定义类型,立刻出问题。
真正可靠的做法是显式提供三参数版本:
int sum = list.parallelStream()
.reduce(
0, // identity:中性值,不可变
(partial, e) -> partial + e, // accumulator:每次加新元素
(left, right) -> left + right // combiner:合并两个分段结果
);
这个结构天然适配 Fork/Join 框架,各线程独立计算片段,最后安全合并。
带状态聚合:用不可变容器替代可变对象
求平均值需要同时记 sum 和 count,不能用 int 当 identity,也不能用 new long[2](数组可变,会被多线程污染)。
正确姿势是定义不可变数据载体:
record SumCount(long sum, long count) {
SumCount add(int x) { return new SumCount(sum + x, count + 1); }
SumCount merge(SumCount other) {
return new SumCount(sum + other.sum, count + other.count);
}
}
再传入 reduce:
SumCount result = stream.reduce(
new SumCount(0, 0),
(sc, x) -> sc.add(x),
(sc1, sc2) -> sc1.merge(sc2)
);
double avg = (double) result.sum() / result.count();
初始值配置的底层逻辑:它不是“起点”,而是“单位元”
很多人把 identity 理解为“第一个参与计算的值”,这是误区。它的本质是代数中的单位元:满足 op(identity, x) == x 且 op(x, identity) == x。
所以:
- 加法用
0,因为0 + x == x - 乘法用
1,因为1 * x == x - 字符串拼接用
"",因为"" + s == s - 集合合并用
new ArrayList()?错!应改用Collectors.toList()或自定义不可变收集器
配错 identity,不仅影响空流结果(如空流返回 identity),更会在并行时破坏分段合并的数学一致性。

















