For Each and Map/Reduce
Explore how to apply Java 8's forEach, map, and reduce methods to process collections more effectively. Learn to replace traditional loops with streams, perform filtering, mapping, and aggregate operations, and find highest values in datasets using functional programming concepts.
We'll cover the following...
For each
The most basic thing we can do with a stream is loop through it using the forEach method.
For example, to print out all of the files in the current directory, we could do the following:
For the most part, this replaces the for loop. It is more concise and more object-oriented since we are delegating the implementation of the actual loop.
Map, filter, and reduce
Lambda expressions and default methods allow us to implement map/filter/reduce in Java 8. Actually, it is already implemented for us in the standard library.
Let’s see how these methods can be implemented:
The above program creates a map with key and value pairs. It then prints them one-by-one.
The above code adds fruits and their prices to a list. It then uses the filter method to print the ones with a price higher than 180.
The code above takes an array of integers and uses the reduce method to find their sum.
Now let’s consider a slightly advanced example. Imagine getting the current point scores from a list of player names and finding the player with the most points. We have a simple class, PlayerPoints. And we have a getPoints method defined as the following:
public static class PlayerPoints {
public final String name;
public final long points;
public PlayerPoints(String name, long points) {
this.name = name;
this.points = points;
}
public String toString() {
return name + ":" + points;
}
}
public static long getPoints(final String name){
// gets the Points for the Player
}
Finding the highest player could be done very simply in Java 8. This is shown in the following code:
PlayerPoints highestPlayer =
names.stream().map(name -> new PlayerPoints(name, getPoints(name)))
.reduce(new PlayerPoints("", 0.0),
(s1, s2) -> (s1.points > s2.points) ? s1 : s2);
Let’s see how this works:
The above program takes a list of player names and generates random points for them. It then finds the player with the highest points. The output shows both the player’s name and points. Java 8 implementation uses map and reduce for this calculation.
This could also be done in Java 7 with the dollar library (or similarly with Guava or Functional- Java), but it would be much more verbose as shown in the following:
PlayerPoints highestPlayer =
$(names).map(new Function < String, PlayerPoints > () {
public PlayerPoints call(String name) {
return new PlayerPoints(name, getPoints(name));
}
})
.reduce(new PlayerPoints("", 0.0),
new BiFunction < PlayerPoints, PlayerPoints, PlayerPoints > () {
public PlayerPoints call(PlayerPoints s1, PlayerPoints s2) {
return (s1.points > s2.points) ? s1 : s2;
}
});
The major benefit to coding this way (apart from the reduction in lines of code) is the ability to hide the underlying implementation of map/reduce. For example, it’s possible that map and reduce are implemented concurrently, allowing us to easily take advantage of multiple processors. We’ll describe one way to do this (ParallelArray) in the following lesson.