Java 8 lambda sum,count和group by

时间:2018-01-22 20:45:42

标签: java java-8 java-stream

Select sum(paidAmount), count(paidAmount), classificationName,
From tableA
Group by classificationName;

如何使用流和收集器在Java 8中执行此操作?

Java8:

lineItemList.stream()
            .collect(Collectors.groupingBy(Bucket::getBucketName,
                       Collectors.reducing(BigDecimal.ZERO,
                                           Bucket::getPaidAmount,
                                           BigDecimal::add)))

这给了我总和和分组。但是我怎样才能指望群组名称?

期望是:

100, 2, classname1 
50, 1, classname2
150, 3, classname3

3 个答案:

答案 0 :(得分:5)

当您使用BigDecimal作为金额(这是正确的方法,IMO)时,您无法使用Collectors.summarizingDouble,它总结了计数,总和,平均值,最小和最大一次通过。

亚历克西斯C.已经用his answer一种方式展示了溪流。另一种方法是编写自己的收集器,如Holger's answer所示。

在这里,我将展示另一种方式。首先,让我们使用辅助方法创建一个容器类。然后,我将使用常见的Map操作,而不是使用流。

class Statistics {
    int count;
    BigDecimal sum;

    Statistics(Bucket bucket) {
        count = 1;
        sum = bucket.getPaidAmount();
    }

    Statistics merge(Statistics another) {
        count += another.count;
        sum = sum.add(another.sum);
        return this;
    }
}

现在,您可以按如下方式进行分组:

Map<String, Statistics> result = new HashMap<>();
lineItemList.forEach(b -> 
    result.merge(b.getBucketName(), new Statistics(b), Statistics::merge));

这可以使用Map.merge方法,其文档说:

  

如果指定的键尚未与值关联或与null关联,则将其与给定的非空值关联。否则,将相关值替换为给定重映射函数的结果

答案 1 :(得分:5)

使用Statistics this answer类的扩展版本,

class Statistics {
    int count;
    BigDecimal sum;

    Statistics(Bucket bucket) {
        count = 1;
        sum = bucket.getPaidAmount();
    }
    Statistics() {
        count = 0;
        sum = BigDecimal.ZERO;
    }

    void add(Bucket b) {
        count++;
        sum = sum.add(b.getPaidAmount());
    }

    Statistics merge(Statistics another) {
        count += another.count;
        sum = sum.add(another.sum);
        return this;
    }
}

您可以在像

这样的Stream操作中使用它
Map<String, Statistics> map = lineItemList.stream()
    .collect(Collectors.groupingBy(Bucket::getBucketName,
        Collector.of(Statistics::new, Statistics::add, Statistics::merge)));

这可能具有较小的性能优势,因为它仅为每个组创建一个Statistics个实例以进行顺序评估。它甚至支持并行评估,但是你需要一个包含足够大的组的非常大的列表来从并行评估中获益。

对于顺序评估,操作等同于

lineItemList.forEach(b ->
    map.computeIfAbsent(b.getBucketName(), x -> new Statistics()).add(b));

而在并行评估后合并部分结果的工作更接近已在链接答案中给出的例子,即

secondMap.forEach((key, value) -> firstMap.merge(key, value, Statistics::merge));

答案 2 :(得分:3)

您可以减少键将保持总和的值,并且值将保持计数:

Map<String, SimpleEntry<BigDecimal, Long>> map = 
    lineItemList.stream()
                .collect(groupingBy(Bucket::getBucketName,
                         reducing(new SimpleEntry<>(BigDecimal.ZERO, 0L), 
                                  b -> new SimpleEntry<>(b.getPaidAmount(), 1L), 
                                  (v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue()))));

虽然Collectors.toMap看起来更干净:

Map<String, SimpleEntry<BigDecimal, Long>> map = 
    lineItemList.stream()
                .collect(toMap(Bucket::getBucketName,
                               b -> new SimpleEntry<>(b.getPaidAmount(), 1L),
                               (v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue())));