I would like to use stream to group, get the sum by type, then find the result of the different by type.
So this is my data set.
Sample(SampleId=1, SampleTypeId=1, SampleQuantity=5, SampleType=ADD),
Sample(SampleId=2, SampleTypeId=1, SampleQuantity=15, SampleType=ADD),
Sample(SampleId=3, SampleTypeId=1, SampleQuantity=25, SampleType=ADD),
Sample(SampleId=4, SampleTypeId=1, SampleQuantity=5, SampleType=SUBTRACT),
Sample(SampleId=5, SampleTypeId=1, SampleQuantity=25, SampleType=SUBTRACT)
Sample(SampleId=6, SampleTypeId=2, SampleQuantity=10, SampleType=ADD),
Sample(SampleId=7, SampleTypeId=2, SampleQuantity=20, SampleType=ADD),
Sample(SampleId=8, SampleTypeId=2, SampleQuantity=30, SampleType=ADD),
Sample(SampleId=9, SampleTypeId=2, SampleQuantity=15, SampleType=SUBTRACT),
Sample(SampleId=10, SampleTypeId=2, SampleQuantity=35, SampleType=SUBTRACT)
My current method of doing is as such:
Map<Long, Integer> result = sampleList.stream()
.collect(
Collectors.groupingBy(
Sample::getSampleTypeId,
Collectors.summingInt(Sample::getSampleQuantity)
)
);
and my result is as such (SampleTypeId, result):
{1=75, 2=110}
I'm not sure how to continue from here. Basically, there is a SampleType of either ADD or SUBTRACT. So I need to SUM all the TypeId=1 and Type=ADD AND SUM all the typeId=1 and Type=SUBSTRACT then find the difference.
Example
1 >> (5+15+25) - (5+25) = 15
2 >> (10+20+30) - (15+35) = 10
So the expected output should be {1=15, 2=10}
.
Collectors are a very powerful feature in Java 8, because each of them can be composed of different blocks, and even then, there is a simple way to compose collectors themselves.
This solution is solved by such
Collector
composition, below I'll guide you through the steps towards the solution.First of all, we start from the first requirement: the result has to be a dictionary keyed with
TypeId
:Second, among each
TypeId
list we need to make sub-groups byType
. For that, we use an extended version of Grouping By collector, which allows us to specify what we should do once we classified elements and assigned them to groups. In out case, we need to classify them again:Now, once both classifiers are done, we need to sum quantities among all the elements within group. The technique is the same: we tell what to do with group elements after classifier is run. In this case, sum all the amounts:
Next, the most interesting part: we need to do an operation on the
Map<Type, Integer>
. More specifically, we need to domap[Add] - map[Sub]
. The most important notion of this step is we want to do it once all the other collecting operations are complete, before returning result. This is an operation which can only happen at the stage of the stream collection calledCollector.finisher
. We can compose finishers of arbitrary collectors by callingcollectingAndThen
method ofCollectors
class:That's all. Note that it's important to call
getOrDefault
instead ofget
, because there might not've been any elements with some specificSampleType
, and map would otherwise returnnull
, causing the whole thing to collapse withNullPointerException
.The outer
groupingBy
groups bysampleTypeId
. At the next level, it involves the construction of map with keySampleType
and value the sum ofSampleQuantity
for a key. The finisher of thecollectingAndThen
subtracts the value of ADD and SUBTRACT to give the final result.Version 2 (even simpler):
It does not construct the second map that was constructed earlier. It simply checks whether the SampleType is ADD or SUBTRACT and returns the sampleQuantity appropriately.
There may be a simpler way with just map:
The following:
is exactly the same as this:
So you could take advantage of this and avoid nested-grouping by
SampleType
. Instead, just either sum or subtract on the downstream collector:Even better, if you could create a method in the
Sample
class:Then use it as follows: