Having the following class:
public class TimeInterval {
private ZonedDateTime time;
private Double value1;
private Double value2;
}
where the TimeInterval.times are intervals in the day what I want to do is group by day and aggregate the values. The tricky part is that I want to apply two different types of aggregations based on a certain condition.
For example:
2018-01-01T00:00, 1.0, 2.0
2018-01-01T08:00, null, null
2018-01-01T16:00, 5.0, 6.0
2018-01-02T00:00, 1.0, 2.0
2018-01-02T08:00, 3.0, 4.0
2018-01-02T16:00, 5.0, 6.0
2018-01-03T00:00, null, null
2018-01-03T08:00, null, null
2018-01-03T16:00, null, null
Should be aggregated to:
2018-01-01, 32.0 - nulls are replaced with 0.0 in this case
2018-01-02, 44.0 - all values valid
2018-01-03, null - all intervals with null values, final value is null
The aggregation function is value1 * value2
but the point is that I want the nulls to be replaced with 0.0 in the case when only part of the intervals are with null values (2018-01-01 from the above example) but if all the intervals are with null values I want the final value for the day to be null (2018-01-03 from the above example).
How do I do that with Java streams?
You can use
Map<LocalDate, Double> map = list.stream()
.collect(groupingBy(ti -> ti.time.toLocalDate(),
collectingAndThen(
filtering(ti -> ti.value1 != null,
mapping(ti -> ti.value1 * ti.value2, reducing(Double::sum))),
o -> o.orElse(null))
));
Normally, you would use the toMap
collector for a Reduction like this, but toMap
does not allow functions to return null
. Further, conditional expressions mixing numeric expressions and null
are hard to understand and can easily lead to exceptions.
So this solution filters null
values first, uses the reducing
collector which produces an Optional
which will be empty when all values were null
, so we can substitute the empty optional with the desired null
result at a final step using orElse(null)
.