Search⌘ K
AI Features

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:

Java
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.Files;
import java.io.File;
import java.io.IOException;
public class Program{
public static void main(String[] args)throws IOException{
Files.list(Paths.get("."))
.forEach(System.out::println);
}
}

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:

Java
import java.util.*;
class Mapexample {
public static void main(String args[]) {
Map < String, Integer > map = new HashMap < String, Integer > ();
map.put("Hello", 23);
map.put("Gary", 123);
map.put("Larry", 145);
//Elements can traverse in any order
for (Map.Entry m: map.entrySet()) {
System.out.println(m.getKey() + " " + m.getValue());
}
}
}

The above program creates a map with key and value pairs. It then prints them one-by-one.

Java
import java.util.*;
class Item {
String name;
int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
}
public class Filterexample {
public static void main(String[] args) {
List < Item > Itemlist = new ArrayList < Item > ();
//Adding items
Itemlist.add(new Item("Apples", 200));
Itemlist.add(new Item("Banana", 150));
Itemlist.add(new Item("Mango", 300));
Itemlist.stream()
.filter(p -> p.price > 180) // filtering price
.map(pm -> pm.price) // fetching price
.forEach(System.out::println); // iterating price
}
}

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.

Java
import java.util.*;
class reduceexample {
public static void main(String[] args)
{
List<Integer> array = Arrays.asList(-5, 5, -6, 6, 0);
// Finding sum of all elements
int sum = array.stream().reduce(0,
(element1, element2) -> element1 + element2);
System.out.println("Sum = " + sum);
}
}

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:

Java
import java.util.Arrays;
import java.util.List;
import java.lang.Math;
public class Program{
public static void main(String[] args){
List<String> names = Arrays.asList("a1", "a2", "b1", "c2", "c1");
// java 8 implementation
PlayerPoints highestPlayer =
names.stream().map(name -> new PlayerPoints(name, getPoints(name)))
.reduce(new PlayerPoints("", 0),
(s1, s2) -> (s1.points > s2.points) ? s1 : s2);
System.out.println(highestPlayer);
}
// class for player data
private 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;
}
}
// generates random points for the players and returns the value
private static long getPoints(final String name){
long points = (long)(Math.random()*100);
return points;
}
}

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.