2

We have the following:

public List<Balance> mapToBalancesWithSumAmounts(List<MonthlyBalancedBooking> entries) {
    return entries
      .stream()
      .collect(
        groupingBy(
          MonthlyBalancedBooking::getValidFor,
          summingDouble(MonthlyBalancedBooking::getAmount)
        )
      )
      .entrySet()
      .stream()
      .map(localDateDoubleEntry -> new Balance(localDateDoubleEntry.getValue(), localDateDoubleEntry.getKey()))
      .collect(toList());
  }

Is there a possibility to avoid the second stream() path in the code, so the result of the groupingBy() should be a list in our case. We need a possibility to pass the map()-function to collect or groupingBy is that possible in Java 8?

Naman
  • 27,789
  • 26
  • 218
  • 353
  • What's your java version? – ernest_k Mar 26 '20 at 07:53
  • Java 8 :( But solutions for more modern Java versions could be posted too for persons with same problem. – Christian Wiedehöft Mar 26 '20 at 08:30
  • @ChristianWiedehöft That's not even possible, with imperative (`for`) style either. Unless the `Balance` objects are mutable and can move around a unique key somehow. I would suggest giving it a try both ways. And what is the reason for moving to a single call to `stream`? – Naman Mar 26 '20 at 08:40
  • You can create your own collector doing the merging in the first pass. – daniu Mar 26 '20 at 09:00

2 Answers2

2

The simple way is just using toMap() collector with merge function like this:

List<Balance> balances = new ArrayList<>(entries.stream()
       .collect(toMap(MonthlyBalancedBooking::getValidFor, m -> new Balance(m.getAmount(),
                                              m.getValidFor()),Balance::merge)).values());

I supposed for Balance class these properties:

class Balance {
   private Double value;
   private Integer key;

   public Balance merge(Balance b) {
     this.value += b.getValue();
     return this;
   }
}
Hadi J
  • 16,989
  • 4
  • 36
  • 62
  • 1
    I did give this a thought, hence the comment. The problem here is the key that you're choosing for the map and then deciding over how to merge. The objects that you're creating with the intermediate `map` operation might just be flawed. – Naman Mar 26 '20 at 09:10
  • 2
    Very elegant solution. The only thing I would change is moving the `merge` method out of POJO. Having a static utility method for this purpose is much more flexible approach. One may have no access to the source code of `Balance` class. – ETO Mar 30 '20 at 04:14
  • 2
    You would also have to add the `Balance::getKey` which didn't exist in the questions code. Or you get rid of the `map` operation and stay closer to the question's code: `entries.stream() .collect(toMap(MonthlyBalancedBooking::getValidFor, m -> new Balance(m.getAmount(), m.getValidFor()), Balance::merge))` – Holger Mar 30 '20 at 12:35
2

That wouldn't be possible since the value that you are looking for as you map to the Balance objects could only be evaluated once all the entries of the MonthlyBalancedBooking list are iterated.

new Balance(localDateDoubleEntry.getValue(), localDateDoubleEntry.getKey())

An alternate way though with moving the stream though within a single terminal operation could be by using collectingAndThen as:

public List<Balance> mapToBalancesWithSumAmounts(List<MonthlyBalancedBooking> entries) {
    return entries.stream()
            .collect(Collectors.collectingAndThen(
                    Collectors.groupingBy(MonthlyBalancedBooking::getValidFor,
                            Collectors.summingDouble(MonthlyBalancedBooking::getAmount)),
                    map -> map.entrySet().stream()
                            .map(entry -> new Balance(entry.getValue(), entry.getKey()))))
            .collect(Collectors.toList());
}
Naman
  • 27,789
  • 26
  • 218
  • 353