Every now and then I need to do some basic stuff in Java and I wonder: "what is the best way to this". This happened to me a few days ago. I needed to do a simple thing as "get the sum of a List of numbers" and I found out there are a number of ways — pun intended — to do this.
The old fashion approach
We can create a simple loop to do this. I am using Java 11 so forgive me if the List.of
and var
does not work in your case when you, for instance, use Java 8. However, you probably will still get the point.
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = 0;
for (int i = 0; i < listOfNumbers.size() ; i++) {
sum += listOfNumbers.get(i);
}
Obviously, since Java 5, we have enhanced for loops so I can rewrite the same code like this.
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = 0;
for (int number : listOfNumbers) {
sum += number;
}
The difference is subtle. However it is already more expressive as it says somehting like: "of each number
comming from listOfNumbers
I want to do the following ..."
The Java Stream approach
People who know me, know that I was brainwashed during my university days with programming Haskell. This means I have a lot of love for pure functional programming. Not that Java can handle that 😉, but the expressiveness of functional programming is somewhat available using the stream API.
With the stream API in Java, we can execute the MapReduce programming model. For the issue I am trying to solve here, I do not need to map as the numbers will stay as they are. I do however have to reduce the List into a single number, the sum.
Collect
In probably 99% of the cases, we probably use the collect function with the standard toList()
collector to reduce our stream back into a List.
Similar to this:
var time2ToList = listOfNumbers.stream()
.map(i -> i * 2)
.collect(Collectors.toList());
However, there is more to life collecting a stream back into a List. Browsing to the Collectors library you will function like summingInt()
, summingDouble()
and summingLong()
You can use these functions to collect (or reduce) the List into the sum.
The summmingInt function does require a function that turns the input you have into an int
. In this case, I can simply use the "identity function". The function i -> i
will be sufficient.
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = listOfNumbers.stream().collect(Collectors.summingInt(i -> i));
This identity function might look silly so can equally use Integer.intValue().
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = listOfNumbers.stream()
.collect(Collectors.summingInt(Integer::intValue));
When I do this, my IDE — IntelliJ IDEA in my case — advise me to refactor this and use the mapToInt()
function like this.
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = listOfNumbers.stream().mapToInt(Integer::intValue).sum();
Technically what we do here is mapping every item to an int
, what it already is ¯\(ツ)/¯ and reduce it with the sum() function.
It gets more clear if you look at the inferred types. You simply cannot have a List of primitives. So the List is a list of Integer (the Object). This means that every item in the list needs to get back to the primitive int
to make the sum()
possible. The previous example with the identity function in the collector works because of Java unboxing.
If you would like to use primitive Lists in Java, I would suggest taking a look at the Eclipse Collections library.
Reduce
Reduction in Java is achieved by a couple of function in the stream API
In addition to collect()
there is also the obviously named function reduce()
var listOfNumbers = List.of(1,2,3,4,5,6,7,8,9,10);
var sum = listOfNumbers.stream().reduce(0 , (num1, num2) -> num1 + num2);
The reduce function in this case takes a starting point and BIFunction lambda expression. The BiFunction will be applied to the starting point and the first number. The result of the function will be applied to the second number and so on.
The code above does something like this.
0 + 1
1 + 2
3 + 3
6 + 4
etc ...
Now, you can omit the starting point 0
. However, the reduce function will return an Optional in this case as the List it tries to reduce might be empty.
Conclusion
As you can see, there are multiple ways to solve this problem. Without any doubt, people will come up with even more exotic ways to solve this. My personal favorite is the reduce()
option. For me, this is the most expressive and pure solution in Java. I simply only want to reduce a list to a single number and don't need to care of the transformations from boxed types to primitives. Furthermore, I can reuse this approach when I need to reduce a List of other types by writing a reduction lambda function that fits my needs.
Top comments (0)