Pau*_*bin 5 java lambda functional-programming java-8 java-stream
我试图了解新的Java 8 Stream API.
http://docs.oracle.com/javase/tutorial/collections/streams/reduction.html
我找到了使用collect API查找数字平均值的示例.但我觉得,使用reduce()也可以做同样的事情.
public class Test {
public static void main(String[] args) {
// Using collect
System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Averager::new, Averager::accept, Averager::combine)
.average());
// Using reduce
System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.reduce(new Averager(), (t, u) -> {
t.accept(u);
return t;
}, (t, u) -> {
t.combine(u);
return t;
}).average());
}
private static class Averager {
private int total = 0;
private int count = 0;
public Averager() {
// System.out.println("Creating averager");
}
public double average() {
// System.out.println("Finding average");
return count > 0 ? ((double) total) / count : 0;
}
public void accept(int i) {
// System.out.println("Accepting " + i);
total += i;
count++;
}
public void combine(Averager other) {
// System.out.println("Combining the averager : " + other);
total += other.total;
count += other.count;
}
@Override
public String toString() {
return "[total : " + total + ", count: " + count + "]";
}
}
}
Run Code Online (Sandbox Code Playgroud)
1)有什么理由,我应该使用collect而不是reduce?
2)如果我启用所有调试系统,我可以看到执行的操作在收集和减少之间完全相同.在两种情况下都没有使用合成器.
3)如果我使流并行,收集总是返回正确的结果.reduce()每次给我不同的结果.
4)我不应该在并行流中使用reduce吗?
谢谢,
保罗
Stu*_*rks 19
reduce和之间的区别在于collect它collect是一种增强的缩减形式,可以并行处理可变对象.该collect算法线程地限制各种结果的对象,使他们能够安全地突变,即使他们不是线程安全的.这就是Averager使用的原因collect.对于顺序计算,使用reduce它通常并不重要,但对于并行计算,它会给出不正确的结果,如您所观察到的那样.
一个关键点是,reduce只要它处理值而不是可变对象就可以工作.你可以通过查看第一个参数来看到这一点reduce.示例代码传递的new Averager()是单个对象,该对象在并行缩减中由多个线程用作标识值.并行流的工作方式是将工作负载拆分为由各个线程处理的段.如果多个线程正在改变相同的(非线程安全的)对象,则应该清楚为什么这会导致不正确的结果.
可以用来reduce计算平均值,但是你需要使你的累积对象不可变.考虑一个对象ImmutableAverager:
static class ImmutableAverager {
private final int total;
private final int count;
public ImmutableAverager() {
this.total = 0;
this.count = 0;
}
public ImmutableAverager(int total, int count) {
this.total = total;
this.count = count;
}
public double average() {
return count > 0 ? ((double) total) / count : 0;
}
public ImmutableAverager accept(int i) {
return new ImmutableAverager(total + i, count + 1);
}
public ImmutableAverager combine(ImmutableAverager other) {
return new ImmutableAverager(total + other.total, count + other.count);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我已经调整了签名accept并combine返回新的ImmutableAverager而不是变异this.(这些更改也使方法与函数参数匹配,reduce因此我们可以使用方法引用.)您可以这样使用ImmutableAverager:
System.out.println(Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.parallel()
.reduce(new ImmutableAverager(),
ImmutableAverager::accept,
ImmutableAverager::combine)
.average());
Run Code Online (Sandbox Code Playgroud)
使用不可变值对象reduce应该并行提供正确的结果.
最后,注意IntStream和DoubleStream有summaryStatistics()方法和Collectors拥有averagingDouble,averagingInt以及averagingLong能为你做这些计算方法.但是,我认为问题更多的是关于收集和减少的机制,而不是关于如何最简洁地进行平均.