前言
Java8
提供的Stream接口使流式编程和函数式编程更加容易。
现在一些集合的处理,经常会使用Stream来进行处理,相比循环,代码的可读性有所提高。如果更进一步,再利用上Reactor
进行反应式编程,则会带来更多优势,如异常处理、执行线程控制、并行、缓冲等,声明式的完成了许多命令式编程许多代码才能完成的功能。
场景
一次使用Stream进行收集的过程中,同时使用与
,出现了问题。这里记录一下。
场景是一批业务对象Foo
:
@Getter
@Setter
@ToString
class Foo{
// 分组属性
private String name;
// 需求:分组平均值
private Integer m;
// 需求:分组平均值
private Integer n;
// 非业务属性,计算平均值时使用
private Integer count;
public Foo(String name, Integer m, Integer n) {
this.name = name;
this.m = m;
this.n = n;
}
}
笔者想根据Stream<Foo>
对这个流先按照name分组收集,再每组统计m,n属性的平均值,最终仍返回一个Foo
,便类似下面这样写了出来:
Foo f1 = new Foo("l", 1, 2),
f2 = new Foo("l", 1, 2),
f3 = new Foo("l", 1, 2),
f4 = new Foo("c", 1, 2),
f5 = new Foo("c", 1, 2),
f6 = new Foo("q", 1, 2),
f7 = new Foo("q", 1, 2);
System.out.println(Stream.of(f1, f2, f3, f4, f5, f6, f7)
.collect(Collectors.groupingBy(
Foo::getName,
Collectors.reducing(new Foo(null, 0, 0), (o, p) -> {
if (o.getName() == null) {
o.setName(p.getName());
}
o.setM(o.getM() + p.getM());
o.setN(o.getN() + p.getN());
o.setCount(o.getCount() == null ? 1 : o.getCount() + 1);
return o;
})
))
);
最后发现结果为:
{q=Foo(name=l, m=7, n=14, count=6), c=Foo(name=l, m=7, n=14, count=6), l=Foo(name=l, m=7, n=14, count=6)}
按照名称分组后各组的Foo
都是一个,代码并未向预想那样,先groupingBy
分组,再按照每组进行reducing
收集,而是把原始Stream
全收集为一个对象给了每组作为值。
原因和解决方案
查了查*,看了下的源码:
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op) {
return new CollectorImpl<>(
// 作为accumulator的函数式接口,看下段
boxSupplier(identity),
(a, t) -> { a[0] = op.apply(a[0], t); },
(a, b) -> { a[0] = op.apply(a[0], b[0]); return a; },
a -> a[0],
CH_NOID);
}
// 重点,每次只返回传进来的对象,而不是新创建一个
private static <T> Supplier<T[]> boxSupplier(T identity) {
return () -> (T[]) new Object[] { identity };
}
发现了, 所创建的
accumulator
返回的是一个传进来的固定对象,不会因为上游是就新创建然后分组,所以最好不要修改
的参数。
最新的需求可以这样解决:
System.out.println(Stream.of(f1, f2, f3, f4, f5, f6, f7)
.collect(Collectors.groupingBy(
Foo::getName,
// 新构建Collector,每组accumulator会新创建Foo
Collector.of(() -> new Foo(null, 0, 0), (o, p) -> {
if (o.getName() == null) {
o.setName(p.getName());
}
o.setM(o.getM() + p.getM());
o.setN(o.getN() + p.getN());
o.setCount(o.getCount() == null ? 1 : o.getCount() + 1);
}, (o, p) -> o)
))
);
输出了想要的结果:
{q=Foo(name=q, m=2, n=4, count=2), c=Foo(name=c, m=2, n=4, count=2), l=Foo(name=l, m=3, n=6, count=3)}
参考
/questions/42981783/java-8-stream-collect-groupingby-mapping-reducing-reducing-b
JDK11.0.2