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
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:
SubsectionD.3.1The 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.
NoteD.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:
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:
SubsectionD.3.2The 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:
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:
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:
And here it is in the context of a program that does both sums and products: