Is mapToDouble() really necessary for summing a List<Double> with Java 8 streams?

JavaJava 8Java Stream

Java Problem Overview


As far as I can tell, the way to sum a List<Double> using Java 8 streams is this:

List<Double> vals = . . . ;
double sum = vals.stream().mapToDouble(Double::doubleValue).sum();

To me, the mapToDouble(Double::doubleValue) seems kind of crufty - just the sort of boilerplate "ceremony" that lambdas and streams were supposed to dispense with.

Best practices tell us to prefer List instances over arrays, and yet for this sort of summing, arrays seem cleaner:

double[] vals = . . . ;
double sum = Arrays.stream(vals).sum();

Granted, one could do this:

List<Double> vals = . . . ;
double sum = vals.stream().reduce(0.0, (i,j) -> i+j);

But that reduce(....) is so much longer than sum().

I get that this has to do with the way streams need to be retrofitted around the Java's non-object primitives, but still, am I missing something here? Is there some way to squeeze autoboxing in to make this shorter? Or is this just the current state of the art?


Update - Answers Digest

Here is a digest of answers below. While I have a summary here, I urge the reader to peruse the answers themselves in full.

@dasblinkenlight explains that some kind of unboxing will always be necessary, due to decisions made further back in the history of Java, specifically in the way generics were implemented and their relationship to the non-object primitives. He notes that it is theoretically possible for the compiler to intuit the unboxing and allow for briefer code, but this has not yet been implemented.

@Holger shows a solution that is very close to the expressiveness I was asking about:

double sum = vals.stream().reduce(0.0, Double::sum);

I was unaware of the new static Double.sum() method. Added with 1.8, it seems intended for the very purpose I was describing. I also found Double.min() and Double.max(). Going forward, I will definitely use this idiom for such operations on List<Double> and similar.

Java Solutions


Solution 1 - Java

> Is there some way to squeeze autoboxing in to make this shorter?

Yes, there is. You can simply write:

double sum = vals.stream().mapToDouble(d->d).sum();

This makes the unboxing implicit but, of course, does not add to efficiency.

Since the List is boxed, unboxing is unavoidable. An alternative approach would be:

double sum = vals.stream().reduce(0.0, Double::sum);

It does not do a mapToDouble but still allows reading the code as “… sum”.

Solution 2 - Java

> To me, the mapToDouble(Double::doubleValue) seems [what] lambdas and streams were supposed to dispense with.

The need to use mapToDouble is a consequence of a decision to implement generics via type erasure, essentially closing the door on any possibility of using primitives inside generics. It is that same decision that made it necessary to create the DoubleStream, IntStream, and LongStream family of classes - to provide a stream-based unboxing.

> Is there some way to squeeze autoboxing in to make this shorter? Or is this just the current state of the art?

Unfortunately, not at this time: although it is theoretically possible for the compiler to figure out that Stream<Double> can be converted to DoubleStream implicitly, in the same way that the primitives are unboxed, this has not been done.

As far as your array-based solution goes, it is the most efficient of the three. However, it is not as flexible as the other two: the one with mapToDouble lets you sum any attribute of a custom class, while the last one lets you perform other types of aggregation.

> reduce(....) is so much longer than sum()

I agree, this approach is worse than mapToDouble in terms of readability.

Solution 3 - Java

Here is another way to do it. If you just need a sum, average, min, max etc. on a list of Double, Integer or Long, you can use one of the available Collectors, e.g.:

List<Double> doubles = Arrays.asList(3.14, 5.15, 4.12, 6.);
System.out.println(
		doubles.stream()
				.collect(Collectors.summingDouble(d -> d))
);

would print 18.41

Note, the method name is summingDouble, there is another method called summarizingDouble, which returns DoubleSummaryStatistics, containing all the basic math operations results.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionsparc_spreadView Question on Stackoverflow
Solution 1 - JavaHolgerView Answer on Stackoverflow
Solution 2 - JavaSergey KalinichenkoView Answer on Stackoverflow
Solution 3 - JavajFreneticView Answer on Stackoverflow