Skip to main content
Logo image

Problem Solving with Algorithms and Data Structures using Java: The Interactive Edition

Section D.3 Other Higher Order Functions

Predicates aren’t the only game in town. The java.util.function package contains a large number of classes for use with lambda expressions (see the Java API
 1 
docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/package-summary.html
as of version 21 for a complete list. We won’t go through all of them, but there are two more that are very useful:

Subsection D.3.1 The Function class

We use this class when we want to process a list, applying a function to each element of the original list to create a new list. For example, we might want to apply the Math.sqrt method to each element of a list of integers to create a new list of the square roots. Or we might want to apply the length() method to each element of a list of strings to create a new list of the string lengths. This is called a map operation.

Note D.3.1.

This use of map is slightly different from the usage when we talk about a HashMap class. In this case, we are using map in the mathematical sense, where a function is said to map its domain to its range.
To accomplish these task, we use the Function<T, R> class. It takes a value of one type and returns a value of another type (or the same type).
Here’s the start of a program that uses Function to implement the map method:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;

public class FunctionalMapping {
    public static <T, R> ArrayList<R> map(ArrayList<T> data,
      Function<T, R> f) {

        ArrayList<R> result = new ArrayList<R>();

        for (T item: data) {
            result.add(f.apply(item));
        }
        return result;
    }
Listing D.3.2.
On line 12, you use apply to invoke the Function that was passed to map.
Here’s the whole program, including the main method that uses our map method:
Listing D.3.3.

Subsection D.3.2 The BiFunction class

When we implemented our keep and map methods, we transformed a list into another list. Sometimes, though, we want to process a list and get a single data element as a result. For example, we may want to find the sum of the elements in a list (or the product, or the sum of squares). We might want to join all the strings in a list with a delimiter. The process of reducing a list to a single value is called a reduce operation.
Let’s look at the typical code for adding up the elements in a list of integers:
Listing D.3.4.
There are two things to note in this code. First, rather than setting result to zero inside the reduceSum method, we are passing zero as the starting value for the sum. This makes our general case easier to write. If we wanted a product, the starting value would be one; in the case of joining strings, our starting value is the empty string.
Second, unlike our mapping operations, we need to keep track of two values: the current item from the list and the accumulated result so far. This means we can’t use Function.apply, which has only one parameter. Instead, we need the BiFunction class; its apply method takes two parameters.
Here’s our functional reduce method that uses BiFunction:
public static <T, R> R reduce(ArrayList<T> data, R start,
    BiFunction<R, T, R> f) {
    R result = start;

    for (T item: data) {
        result = f.apply(result, item);
    }
    return result;
}
Listing D.3.5.
Here, we are using two generic types: one type T for the elements of the list, and another type R for the starting value and accumulated result, which must both be of the same type. (The definition of BiFunction uses three different generic types if you need to have different data types for the two parameters, but we don’t need that capability here.)
On line 2, we specify that our reducing bi-function f has the result type as its first parameter, the list type as its second parameter, and returns a result type. This is, indeed, what happens in line 6.
This is what the lambda expression and its usage looks like:
BiFunction<Integer, Integer, Integer> reduceSum =
  (Integer result, Integer n)  -> {
    result += n;
    return result;
};

// using the preceding lambda:
Integer sum = reduce(numbers, 0, reduceSum);
Listing D.3.6.
And here it is in the context of a program that does both sums and products:
Listing D.3.7.
You have attempted of activities on this page.