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

How can i do this in Java 8 using streams and collectors?

Java8:

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

This gives me sum and group by. But how can I also get count on the group name ?

Expectation is :

100, 2, classname1 
50, 1, classname2
150, 3, classname3
1
  • can you elaborate or give me example please Commented Jan 22, 2018 at 21:11

3 Answers 3

6

Using an extended version of the Statistics class of 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;
    }
}

you can use it in a Stream operation like

Map<String, Statistics> map = lineItemList.stream()
    .collect(Collectors.groupingBy(Bucket::getBucketName,
        Collector.of(Statistics::new, Statistics::add, Statistics::merge)));

this may have a small performance advantage, as it only creates one Statistics instance per group for a sequential evaluation. It even supports parallel evaluation, but you’d need a very large list with sufficiently large groups to get a benefit from parallel evaluation.

For a sequential evaluation, the operation is equivalent to

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

whereas merging partial results after a parallel evaluation works closer to the example already given in the linked answer, i.e.

secondMap.forEach((key, value) -> firstMap.merge(key, value, Statistics::merge));
Sign up to request clarification or add additional context in comments.

Comments

5

As you're using BigDecimal for the amounts (which is the correct approach, IMO), you can't make use of Collectors.summarizingDouble, which summarizes count, sum, average, min and max in one pass.

Alexis C. has already shown in his answer one way to do it with streams. Another way would be to write your own collector, as shown in Holger's answer.

Here I'll show another way. First let's create a container class with a helper method. Then, instead of using streams, I'll use common Map operations.

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;
    }
}

Now, you can make the grouping as follows:

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

This works by using the Map.merge method, whose docs say:

If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function

Comments

3

You could reduce pairs where the keys would hold the sum and the values would hold the count:

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()))));

although Collectors.toMap looks cleaner:

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())));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.