使用Java 8流聚合信息

时间:2015-01-16 01:21:03

标签: java java-8 java-stream

我仍然在努力完全掌握使用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)));

并合并两张地图以得出我正在寻找的列表。

1 个答案:

答案 0 :(得分:1)

你走在正确的轨道上。 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());