B Java Distilled
B.7 Predefined Functional Interfaces¶
The Java standard library defines a variety of common functional interfaces in the java.util.functional package. We use a few of them in this book:
-
• A consumer is a function that takes a single argument but doesn’t return anything:
Consumers are commonly used in combination with algorithms that produce a sequence of values:
static void produceRange(int n, Consumer<Integer> consumer) { for (int i = 0; i < n; i++) consumer.accept(i); }By changing the type of consumer we pass to produceRange(), we can now perform different actions whenever a new number is generated:
produceRange(10, System.out::println); // print 0..10 var list = new ArrayList<Integer>(); produceRange(1000, list::add); // add 0..999 to list
The first example prints each number by itself, whereas the second examples collects all numbers in a list of integers.
-
• The opposite of a consumer is a supplier, a function that takes no arguments and produces a single value of type T:
public interface Supplier<T> { T get(); }As a practical example, the following function computes a list of n elements that are initialized using successive values generated by the supplier provided as the second argument:
static <T> List<T> take(int n, Supplier<T> supplier) { List<T> result = new ArrayList<>(n); for (int i = 0; i < n; i++) result.add(supplier.get()); return result; }Any function that doesn’t take any arguments and returns a value can be used as a Supplier. For example, the nextInt() method provided by Random returns a random integer value, so we can create ten random integers as follows:
We can also define suppliers using lambda expressions. To produce a list of ten random numbers less than six, we write
A lambda expression that doesn’t take any arguments is distinguished by an empty argument list () before the arrow symbol ->.
In some situations, you can also think of the new operator as a supplier that produces an object of a given type every time it is called. The special syntax ClassName::new can be used to create a method reference that calls the default constructor of the specified class. For example, we can use take() to create ten instances of the Random class using the method reference Random::new:
Every time take() calls the method reference created by Random::new, the default constructor of Random is invoked, so the code above creates ten distinct random number generators. It is also possible to reference the new operator of generic classes. In this case, the type arguments must either be specified explicitly as part of the class name or it must be possible to infer it from the context. Consider the following example:
List<List<Integer>> listOfLists = take(10, ArrayList::new); var listOfLists2 = take(10, ArrayList<Integer>::new);
Both calls to take() create a list that contains ten empty ArrayLists. In the first line, Java is able to infer that the type argument of ArrayList must be Integer because that’s the only way to make the return value of take() compatible with List
- >
-
• A predicate is a function that tests whether a value or object has a certain property. Accordingly, the functional interface Predicate has a single method test() that takes a single argument and returns a Boolean value:
interface Predicate<T> { boolean test(T value); }Predicates are often used to specify search conditions or termination criteria. For example, the following function findFirstIndex() searches a list for the first entry that satisfies the specified condition:
static int findFirstIndex(List<Integer> list, Predicate<Integer> condition) { for (int i = 0; i < list.size(); i++) { if (condition.test(list.get(i))) return i; } return -1; }If we want to find the first even integer in a list, for example, we can call findFirstIndex() with a predicate that tests whether a given number is even:
int index = findFirstIndex(List.of(1, 2, 3, 4), v -> v % 2 == 0); -
• A comparator is a function that performs a three-way comparison. It compares two objects of the same type and returns an integer that indicates their relative magnitude:
public interface Comparator<T> { int compare(T o1, T o2); }By convention, the return value is negative if the first value is smaller, positive if it is larger, and zero if the two values are equal. This makes it easy to express conventional comparison operators such as \(<\), \(\le \), and \(=\) using compare():
\(p<q\) becomes compare(p, q) < 0 \(p\le q\) becomes compare(p, q) <= 0 \(p= q\) becomes compare(p, q) == 0 and likewise for the remaining operators \(>\), \(\ge \), and \(\not =\).
All the functional interfaces we have discussed so far and a few additional ones are summarized in Table B.3.
| Interface | Main function |
Description |
| Consumer |
void accept(T value) |
consume a value of type T |
| BiConsumer |
void accept(T t, U u) |
consume two values |
| Supplier |
T get() |
produce a value of type T |
| Predicate |
boolean test(T value) | |
| BiPredicate |
void test(T t, U u) |
perform a test on a pair of values |
| Function |
R apply(T value) |
map a value of type T to a result of type R |
| UnaryOperator |
T apply(T value) |
map a value of type T to a result of the same type |
| BinaryOperator |
T apply(T v1, T v2) |
map pairs of values of type T to a result of the same type |
| Comparator |
int compare(T v1, T v2) |
perform a three-way comparison of v1 and v2 |