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.0
isn’t quite clear.First, if you want to perform reduction on the groups, you should provide the
Collector
for the values as a second argument to thegroupingBy
collector. Then, it doesn’t have to deal with neither,Map
norMap.Entry
.Since it’s basically folding the entities to a single number (for each group), you can use an existing collector, i.e.
summingInt
orsummingDouble
.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
EntityAccumulator
contains 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
Collector
interface 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
EntityAccumulator
class (assuming,result
should beint
and0.0
is 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.