Bradley N. Miller, David L. Ranum, Roman Yasinovskyy, J. David Eisenberg
SectionD.3Other 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
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.
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.
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.
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.
publicstatic<T,R>Rreduce(ArrayList<T> data,R start,BiFunction<R,T,R> f){R result = start;for(T item: data){
result = f.apply(result, item);}return result;}
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.
BiFunction<Integer,Integer,Integer> reduceSum =(Integer result,Integer n)->{
result += n;return result;};// using the preceding lambda:Integer sum =reduce(numbers,0, reduceSum);
And here it is in the context of a program that does both sums and products: