我正在尝试了解新的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 + "]";
}
}
}
1)有什么理由,我应该使用collect而不是reduce吗? 2)如果我启用所有调试系统,我可以看到执行的操作在收集和减少之间完全相同。在这两种情况下都没有使用合成器 3)如果我使流并行,收集总是返回正确的结果。 reduce()每次给我不同的结果 4)我不应该在并行流中使用reduce吗?
谢谢,
保罗
答案 0 :(得分:18)
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);
}
}
请注意,我已调整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());
使用reduce
的不可变值对象应该并行提供正确的结果。
最后,请注意IntStream
和DoubleStream
有summaryStatistics()
种方法,Collectors
有averagingDouble
,averagingInt
和averagingLong
可以为您完成这些计算的方法。但是,我认为问题更多的是关于收集和减少的机制,而不是关于如何最简洁地进行平均。