I have a collection constisting of Map<Pair<DateTime, String>, List<Entity>> which was previously grouped using streams. Entity is a simple class with int property and getValue() method.
Now, I want to aggregate values of Entity with usage of my simple EntityAccumulator modyfing the type of the previous map to Map<Pair<DateTime, String>, EntityAccumulator>. The only way to achieve this as far as I understand is to create my own custom collector, howevere I've stucked at finisher() method which should return Pair.
Or, maybe is there simpler way to achieve the result I want ?
StreamProcessing
Map<Pair<DateTime, String>, EntityAccumulator> collect = entities.stream()
.collect(Collectors.groupingBy(entity-> Pair.of(entity.getTimestamp(), entity.getName())))
.entrySet().stream()
.collect(new EntityCollector()));
EntityAccumulator
private static class EntityAccumulator {
private int result = 0.0;
public EntityAccumulator() { }
public EntityAccumulator(int result) {
this.result = result;
}
public void calculate(Entity entity) {
result += entity.getValue();
}
public EntityAccumulatoradd(EntityAccumulator other) {
return new EntityAccumulator(this.result + other.result);
}
}
Collector
public class EntityCollector implements Collector<Map.Entry<Pair<DateTime, String>, List<Entity>>, EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> {
@Override
public Supplier<EntityAccumulator> supplier() {
return EntityAccumulator::new;
}
@Override
public BiConsumer<EntityAccumulator, Map.Entry<Pair<DateTime, String>, List<Entity>>> accumulator() {
return (result, pairListEntry) -> pairListEntry.getValue().forEach(result::calculate);
}
@Override
public BinaryOperator<EntityAccumulator> combiner() {
return EntityAccumulator::add;
}
@Override
public Function<EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> finisher() {
return (k) -> {
return null; // ??? HELP HERE
}
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
Apparently, you actually want to do
or
depending on the actual value type. Your declaration
int result = 0.0isn’t quite clear.First, if you want to perform reduction on the groups, you should provide the
Collectorfor the values as a second argument to thegroupingBycollector. Then, it doesn’t have to deal with neither,MapnorMap.Entry.Since it’s basically folding the entities to a single number (for each group), you can use an existing collector, i.e.
summingIntorsummingDouble.When you create your own collector, you can’t reconstitute information in the finisher function that you have dropped in the accumulator function. If your container type
EntityAccumulatorcontains a single number only, there is no way to produce aMap.Entry<Pair<DateTime, String>, EntityAccumulator>from it.By the way, you rarely need to implemented the
Collectorinterface with a class, even when creating a custom collector. You can simply useCollector.of, specifying the functions and characteristics, to create aCollector.So using your original
EntityAccumulatorclass (assuming,resultshould beintand0.0is a typo), you could useto achieve the same as above. It would also be possible to perform the operation in two steps, like in your attempt, using
but, of course, this is only for completeness. The solution shown at the beginning of this answer is simpler and more efficient.