I have a List with items, where each item is an object with a getter method I'm interested in. I want to run over the complete list to sum all those getter results.
When I do it with java 8 streams, it looks like this:
double currentProduction = itemList.stream().collect(
Collectors.summingDouble((e) -> e.getProduction(param)));
In plain old java, it looks like this:
for (Item item : itemList) {
currentProduction += item.getProduction(param);
}
Both methods yield exactly the same result, but my logger reports that for each item instance the getProduction() method is run TWICE in case of the java 8 stream solution. In the plain old java list iteration solution the getProduction method is just run once per instance, as expected.
As the getProduction method is quite costly, this is an issue for me.
Why is this? And what can I do about this (besides using just the for loop)?
It is bug in implementation of Collectors.summingDouble
.
You can use itemList.stream().mapToDouble(Class1::getProduction).sum()
instead.
Details of bug:
Source of Collector
implementation.
return new CollectorImpl<>(
() -> new double[3],
(a, t) -> { sumWithCompensation(a, **mapper.applyAsDouble(t)**);
a[2] += **mapper.applyAsDouble(t)**;},
(a, b) -> { sumWithCompensation(a, b[0]);
a[2] += b[2];
return sumWithCompensation(a, b[1]); },
a -> computeFinalSum(a),
CH_NOID);
Error is marked by **
. They invoke mapper.applyAsDouble(t)
twice.
Here a small MCVE to demonstrate the issue addressed in bug report JDK-8151123 which is mentioned in the answer from talex
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class SO {
static class CoffeeBabe {
final Double value;
CoffeeBabe(Double value) {
this.value = value;
}
Double getProduction() {
System.out.println("getProduction() for " + this.value);
return value;
}
}
public static void main(String[] args) {
List<CoffeeBabe> itemList = Arrays.asList(
new CoffeeBabe(13.0),
new CoffeeBabe(14.0),
new CoffeeBabe(15.0)
);
System.out.println("mapToDouble...");
double sum = itemList.stream()
.mapToDouble(CoffeeBabe::getProduction).sum();
System.out.println("sum = " + sum);
System.out.println("\nCollectors.summingDouble");
sum = itemList.stream().collect(Collectors.summingDouble(
CoffeeBabe::getProduction));
System.out.println("sum = " + sum);
}
}
output
mapToDouble...
getProduction() for 13.0
getProduction() for 14.0
getProduction() for 15.0
sum = 42.0
Collectors.summingDouble
getProduction() for 13.0
getProduction() for 13.0
getProduction() for 14.0
getProduction() for 14.0
getProduction() for 15.0
getProduction() for 15.0
sum = 42.0
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With