ni yanwen
ni yanwen

Reputation: 33

Sum different values in one java stream lambda fuction

I have a use case need to sum different feilds in this object so I code like this, can I put it in one stream?

int totalUnits = records.stream()
        .mapToInt(DynamoSalesAggregateSummaryRecord::getUnits).sum();
int totalReturns = records.stream()
        .mapToInt(DynamoSalesAggregateSummaryRecord::getReturns).sum();
int totalCancellations = records.stream()
        .mapToInt(DynamoSalesAggregateSummaryRecord::getCancellations).sum();
BigDecimal totalRevenue = records.stream()
        .map(DynamoSalesAggregateSummaryRecord::getRevenue)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal totalRoyalties = records.stream()
        .map(DynamoSalesAggregateSummaryRecord::getRoyalties)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

Upvotes: 1

Views: 168

Answers (3)

GhostCat
GhostCat

Reputation: 140427

Honestly, I would not do that. Instead: get rid of the code duplication in the first place: write a little helper that invokes:

return records.stream().mapToInt(accessorMethod).sum();

(where you pass the accessor to that method).

Then you could go for:

int totals = fetch(DynamoSalesAggregateSummaryRecord::getUnits) + fetch(DynamoSalesAggregateSummaryRecord::getReturns) + ...

But beyond that - it is actually not clear what you intend to do here. The above example assumed that you would build a "total" sum of all entries. If that is the not the case - and you want "distinct" sums, my advise would be to give up on using mapToInt(). And instead do something like:

class SumHelper {
  private int unitTotals = 0;
  private int returnTotals = 0;
  ...

  public add(DynamoSalesAggregateSummaryRecord record) {
    unitTotals += record.getUnits();
    ...

to be used like:

  SumHelper helper = new SumHelper;
  records.forEach(r -> helper.add(r));

In other words: you want to call multiple different methods on a record; to build different sums. You can't do that with streams - as one operation is creating multiple results. So you need some other kind of "storage" to keep track of the different results.

Upvotes: 5

Ivan Pronin
Ivan Pronin

Reputation: 1896

Another approach would be to create a separate method getTotalSum either in DynamoSalesAggregateSummaryRecord class or (better) - external helper. That method sums up all required fields of a single DynamoSalesAggregateSummaryRecord instance, then just sum it up:

int totalSum = 
records.stream()
        .mapToInt(DynamoSalesAggregateSummaryRecord::getTotalSum).sum()

Upvotes: 2

davidxxx
davidxxx

Reputation: 131336

java.util.stream.IntStream.sum() is a terminal operation.
You cannot invoke another stream task after a terminal operation.

If you don't have lot of elements in the collection, the GhostCat solution is fine.
If it is not the case, to avoid iterating 5 times on the records collection, another approach should be used.

You could use streams to do it but I am not sure that you will gain in readability as you should probably write your own code to compute aggregates.

A classic loop seems fine in this context :

int totalUnits = 0;
int totalReturns = 0;
...
for (DynamoSalesAggregateSummaryRecord record : records){

     totalUnits += record.getUnits();
     totalReturns += record.getReturns();
     ...
}

Upvotes: 1

Related Questions