1

I have Class

public class SomeClass {

    private String date;
    private int players;
    private int currency;

// getters setters omitted for brevity
}

I have collection of this objects. Now I want to group by date this objects and sum players count and set currencyId to 0. I do it by this way:

list = list.stream()
           .collect(
                    groupingBy(SomeClass::getDate,
                              collectingAndThen(reducing((a, b) -> {
                                  a.setDate(a.getDate());
                                  a.setPlayers(a.getPlayers() + b.getPlayers());
                                  a.setCurrency(0);
                                  return a;
                              }), Optional::get)))
           .values();

Every thing is fine except when I have only one object of certain date. That object doesnt set currency to 0 (because reducing don`t work if object is only one.) So this is my problem.

If I have:

   Object1 ("11.09", 12, 12)
   Object2 ("11.09", 8, 13)
   Object3 ("12.09", 1, 2)
   Object4 ("12.09", 0, 1)
   Object5 ("13.09", 12, 12)

The output must be:

   Object6 ("11.09", 20, 0)
   Object7 ("12.09", 1, 0)
   Object8 ("13.09", 12, **0**)

But instead:

   Object6 ("11.09", 20, 0)
   Object7 ("12.09", 1, 0)
   Object8 ("13.09", 12, **12**)

Help pls

Anton Kolosok
  • 482
  • 9
  • 24
  • Do you really care about that field value? Do you need object instances back? Wouldn't you rather get a `Map` with the player counts for each day? – Thilo Nov 27 '19 at 08:28
  • 1
    `a.setDate(a.getDate())` is thankfully a no-op. If you actually changed the grouping key inside of the reducer, that could break your map. – Thilo Nov 27 '19 at 08:30
  • 6
    Reduction is not a tool for manipulating the source objects. Besides that, the collector pattern `groupingBy(f1, collectingAndThen(reducing(f2), Optional::get)` can be simplified to `toMap(f1, Function.identity(), f2)`. – Holger Nov 27 '19 at 08:31
  • @Holger What is the benefit to use toMap instead of groupBy? – Anton Kolosok Nov 27 '19 at 10:54
  • 2
    @AntonKolosok you mean, besides being half the code and not needing to run over the result map to call `Optional::get` on each group? – Holger Nov 27 '19 at 11:06
  • @Holger I don`t argue, its very good solution, but ye, besides that. – Anton Kolosok Nov 27 '19 at 11:11
  • 1
    There is no other difference. Both collectors produce a map and often, both can be used to produce the same result, but depending on the use case, one might be better suited than the other. See also [this Q&A](https://stackoverflow.com/q/57041896/2711488) – Holger Nov 27 '19 at 11:17
  • @Holger for multiple references to the [linked Q&A](https://stackoverflow.com/questions/57041896/java-streams-replacing-groupingby-and-reducing-by-tomap)(myself as well), I believe it makes sense to reopen the question and have cast my vote there. – Naman Nov 27 '19 at 14:54

1 Answers1

1

Currency is not getting set to 0 as reducing will not be evaluated for single sigle result. If you want to set all currency to 0, map it 0 as below,

list.stream().map(ele->{ele.setCurrency(0);return ele;}).collect(
        groupingBy(SomeClass::getDate,
                collectingAndThen(reducing((a, b) -> {
                    a.setPlayers(a.getPlayers() + b.getPlayers());
                    return a;
                }), Optional::get)))
        .values();

As correctly pointed by @Holger, you may want to use toMap,

list.stream()
    .map(ele->{ele.setCurrency(0);return ele;})
    .collect(toMap(SomeClass::getDate, Function.identity(), (a, b) -> {
        a.setPlayers(a.getPlayers() + b.getPlayers());
        return a;
    })).values();

Hope it helps.

Vikas
  • 6,868
  • 4
  • 27
  • 41