Issue
I want to create a generic method to calculate summation of values in the given List
.
Instead of writing code below:
final BigDecimal spend = forecasts.stream()
.filter(forecast -> Objects.nonNull(forecast.getPrice()))
.map(PartForecastSupplierDto::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
I want to write code like this:
public <T, R> R sum(Collection<T> collection,
Predicate<? super T> predicate,
Function<? super T, ? extends R> mapper,
R identity, final BinaryOperator<R> accumulator) {
return CollectionUtils.emptyIfNull(collection)
.stream()
.filter(predicate)
.map(mapper)
.reduce(identity, accumulator);
}
However, I'm getting an error like this:
How can I fix this error?
Solution
When you have a chain of method invocations, the type inference will not use information of a later invocation for the preceding invocation’s target type.
Therefore, the .map(mapper)
produces a Stream<? extends R>
regardless of the type inferred for the subsequent reduce
operation.
The simplest way to fix this, is to provide an explicit type for the map
step, i.e.
public <T, R> R sum(Collection<T> collection,
Predicate<? super T> predicate,
Function<? super T, ? extends R> mapper,
R identity, final BinaryOperator<R> accumulator) {
return collection
.stream()
.filter(predicate)
.<R>map(mapper)
.reduce(identity, accumulator);
}
Note that I have strong objections against the policy indicated by the method name emptyIfNull
. A collection should not become null
and you should reject null
for collections as early as possible, to eliminate the cases. Silently doing something useful with null
means protracting the problem and requiring more and more of those conditionals all over the place, instead of fixing the one source of null
.
Note further that your method is not summing anymore, but performing an arbitrary Reduction, so the name doesn’t fit. Also, your specific use case is testing the property it will map to, so it would be simpler the swap the order of the steps
final BigDecimal spend = forecasts.stream()
.map(PartForecastSupplierDto::getPrice)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
which raises questions about the usefulness of a utility method that has a fixed constellation of filter
, map
, and reduce
steps.
Answered By - Holger
Answer Checked By - David Goodson (JavaFixing Volunteer)