Java 使用reduce和collect查找平均值

Java 使用reduce和collect查找平均值,java,lambda,functional-programming,java-8,java-stream,Java,Lambda,Functional Programming,Java 8,Java Stream,我试图理解新的Java8流API 我找到了使用CollectAPI查找数字平均值的示例。但是我觉得,使用reduce()也可以做到这一点 1) 有什么理由我应该在这里使用collect而不是reduce吗? 2) 如果我启用所有调试系统输出,我可以看到在、collect和reduce之间执行的操作完全相同。在这两种情况下,根本没有使用组合器。 3) 如果我使流并行,collect总是返回正确的结果。reduce()每次都给我不同的结果。 4) 我不应该在并行流中使用reduce吗 谢谢, Pa

我试图理解新的Java8流API

我找到了使用CollectAPI查找数字平均值的示例。但是我觉得,使用reduce()也可以做到这一点

1) 有什么理由我应该在这里使用collect而不是reduce吗?
2) 如果我启用所有调试系统输出,我可以看到在、collect和reduce之间执行的操作完全相同。在这两种情况下,根本没有使用组合器。
3) 如果我使流并行,collect总是返回正确的结果。reduce()每次都给我不同的结果。
4) 我不应该在并行流中使用reduce吗

谢谢,

Paul

reduce和collect之间的区别在于,
collect
是一种增强的简化形式,可以并行处理可变对象。
collect
算法线程限制各种结果对象,这样即使它们不是线程安全的,也可以安全地对它们进行变异。这就是为什么
Averager
使用
collect
工作的原因。对于使用
reduce
的顺序计算,这通常并不重要,但是对于并行计算,正如您所观察到的,它将给出错误的结果

一个关键点是,
reduce
只要处理的是,而不是可变对象,它就可以工作。通过查看
reduce
的第一个参数可以看到这一点。示例代码传递
new Averager()
,它是一个单个对象,在并行简化中被多个线程用作标识值。并行流的工作方式是将工作负载分成由单个线程处理的段。如果多个线程正在变异同一个(非线程安全)对象,那么应该清楚为什么这会导致不正确的结果

可以使用
reduce
计算平均值,但需要使累加对象不可变。考虑一个对象<代码> imutabalAlvase<代码>:

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
,而不是对该
进行变异。(这些更改还使方法将函数参数与
reduce
匹配,因此我们可以使用方法引用。)您可以像这样使用
ImmutableAverager

    double average = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .parallel()
            .reduce(new ImmutableAverager(), 
                    ImmutableAverager::accept,
                    ImmutableAverager::combine)
            .average();
    System.out.println("Average: "+average);
将不可变值对象与
reduce
一起使用应该可以并行地给出正确的结果


最后,请注意,
IntStream
DoubleStream
summaryStatistics()
方法,
collector
averagingdoull
averagingingint
方法可以为您进行这些计算。然而,我认为问题更多的是收集和还原的机制,而不是如何最简洁地进行平均。

感谢您提供如此详细的答案。一个小小的更正:收集实际上不是还原的专门化,相反。任何缩减都可以表示为一个集合,但是没有通用的方法将集合表示为缩减(或者至少没有方法不强制客户机代码管理并发性)。所以事实上,还原是一种特殊的收集形式。
    double average = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
            .parallel()
            .reduce(new ImmutableAverager(), 
                    ImmutableAverager::accept,
                    ImmutableAverager::combine)
            .average();
    System.out.println("Average: "+average);