我仍在尝试完全掌握Java 8中Stream包的使用,并希望能获得一些帮助。

我有一个如下所述的类,该类的实例作为数据库调用的一部分在列表中接收。

class VisitSummary {
    String source;
    DateTime timestamp;
    Integer errorCount;
    Integer trafficCount;
    //Other fields
}


为了生成一些可能有用的信息,我有一个类VisitSummaryBySource,它保存所有访问的总数(在给定的时间范围内):

class VisitSummaryBySource {
    String sourceName;
    Integer recordCount;
    Integer errorCount;
}


我希望构建一个List<VisitSummaryBySource>集合,顾名思义,它包含每个不同来源的VisitSummaryBySource对象列表,其中包含记录和遇到的错误的总数。

有没有一种方法可以在单个操作中使用流来实现此目的?还是我需要将其分解为多个操作?我能想到的最好的是:

Map<String, Integer> recordsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource,
                    Collectors.summingInt(VisitSummaryBySource::getRecordCount)));


并计算误差

Map<String, Integer> errorsBySrc = data.parallelStream().collect(Collectors.groupingBy(VisitSummaryBySource::getSource,
                    Collectors.summingInt(VisitSummaryBySource::getErrorCount)));


并合并两个地图以提供我要查找的列表。

最佳答案

您走在正确的轨道上。 Collectors.summingInt的用途是外部groupingBy收集器的下游收集器的示例。此操作从同一组中的每个VisitSummaryBySource实例提取整数值之一,并将其​​求和。这实质上是对整数的减少。

您注意到的问题是,您只能提取/减少一个整数值,因此您必须执行第二遍以提取/减少其他整数值。

关键是要考虑不对单个整数值进行归约,而是对整个VisitSummaryBySource对象进行归约。归约采用BinaryOperator,它采用所讨论类型的两个实例并将它们组合为一个实例。通过向VisitSummaryBySource添加静态方法来执行以下操作:

static VisitSummaryBySource merge(VisitSummaryBySource a,
                                  VisitSummaryBySource b) {
    assert a.getSource().equals(b.getSource());
    return new VisitSummaryBySource(a.getSource(),
                                    a.getRecordCount() + b.getRecordCount(),
                                    a.getErrorCount() + b.getErrorCount());
}


请注意,我们实际上并没有合并源名称。由于此缩减仅在源名称相同的组内执行,因此我们断言只能合并名称相同的两个实例。我们还假定明显的构造函数采用名称,记录计数和错误计数,然后调用该方法来创建合并的对象,其中包含计数的总和。

现在我们的流看起来像这样:

    Map<String, Optional<VisitSummaryBySource>> map =
        data.stream()
            .collect(groupingBy(VisitSummaryBySource::getSource,
                                reducing(VisitSummaryBySource::merge)));


请注意,这种归约会产生Optional<VisitSummaryBySource>类型的映射值。这有点奇怪;我们将在下面处理。我们可以通过使用采用身份值的另一种形式的Optional收集器来避免reducing。这是可能的,但有点荒谬,因为标识的源名称没有很好的价值。 (我们可以使用诸如空字符串之类的东西,但是我们必须放弃这样的主张,即我们只合并源名称相同的对象。)

我们并不真正在乎地图。它只需要保持足够长的时间以减少VisitSummaryBySource实例。完成后,我们可以使用values()提取地图值并丢弃地图。

我们还可以将其转换为流,并通过Optional映射Optional::get来展开它们。这是安全的,因为除非该组中至少有一个成员,否则值永远不会出现在地图中。

最后,我们将结果收集到一个列表中。

最终代码如下所示:

    List<VisitSummaryBySource> output =
        data.stream()
            .collect(groupingBy(VisitSummaryBySource::getSource,
                                reducing(VisitSummaryBySource::merge)))
            .values().stream()
            .map(Optional::get)
            .collect(toList());

10-04 13:41