Chapter 2. The java.util.function Package

The previous chapter discussed the basic syntax of lambda expressions and method references. One basic principle is that for either, there is always a context. Lambda expressions and method references are always assigned to functional interfaces, which provide information about the single abstract method being implemented.

While many interfaces in the Java standard library contain only a single, abstract method and are thus functional interfaces, there is a new package that is specifically designed to contain only functional interfaces that are reused in the rest of the library. That package is called java.util.function.

The interfaces in java.util.function fall into four categories: (1) consumers, (2) suppliers, (3) predicates, and (4) functions. Consumers take a generic argument and return nothing. Suppliers take no arguments and return a value. Predicates take an argument and return a boolean. Functions take a single argument and return a value.

For each of the basic interfaces, there are several related ones. For example, Consumer has variations customized for primitive types (IntConsumer, LongConsumer, and Dou⁠bleConsumer) and a variation (BiConsumer) that takes two arguments and returns void.

Although by definition the interfaces in this chapter only contain a single abstract method, most also include additional methods that are either static or default. Becoming familiar with these methods will make your job as a developer easier.

2.1 Consumers

Problem

You want to write lambda expressions that implement the java.util.function⁠.Consumer package.

Solution

Implement the void accept(T t) method using a lambda expression or a method reference.

Discussion

The java.util.function.Consumer interface has as its single, abstract method, void accept(T t). See Example 2-1.

Example 2-1. Methods in java.util.function.Consumer
        void        accept(T t)                        1
default Consumer<T> andThen(Consumer<? super T> after) 2
1

Single abstract method

2

Default method for composition

The accept method takes a generic argument and returns void. One of the most frequently used examples of a method that takes a Consumer as an argument is the default forEach method in java.util.Iterable, shown in Example 2-2.

Example 2-2. The forEach method in Iterable
default void forEach(Consumer<? super T> action)  1
1

Passes each element of an iterable collection to the consumer argument

All linear collections implement this interface by performing the given action for each element of the collection, as in Example 2-3.

The lambda expression conforms to the signature of the accept method, because it takes a single argument and returns nothing. The println method in PrintStream, accessed here via System.out, is compatible with Consumer. Therefore, either can be used as the target for an argument of type Consumer.

The java.util.function package also contains primitive variations of Consumer<T>, as well as a two-argument version. See Table 2-1 for details.

Table 2-1. Additional Consumer interfaces
Interface Single abstract method

IntConsumer

void accept(int x)

DoubleConsumer

void accept(double x)

LongConsumer

void accept(long x)

BiConsumer

void accept(T t, U u)

Tip

Consumers are expected to operate via side effects, as shown in Recipe 2.3.

The BiConsumer interface has an accept method that takes two generic arguments, which are assumed to be of different types. The package contains three variations on BiConsumer where the second argument is a primitive. One is ObjIntConsumer, whose accept method takes two arguments, a generic and and an int. ObjLong​Cons⁠umer and ObjDoubleConsumer are defined similarly.

Other uses of the Consumer interface in the standard library include:

Optional.ifPresent(Consumer<? super T> consumer)

If a value is present, invoke the specified consumer. Otherwise do nothing.

Stream.forEach(Consumer<? super T> action)

Performs an action for each element of the stream.1 The Stream.forEachOrdered method is similar, accessing elements in encounter order.

Stream.peek(Consumer<? super T> action)

Returns a stream with the same elements as the existing stream, first performing the given action. This is a very useful technique for debugging (see Recipe 3.5 for an example).

See Also

The andThen method in Consumer is used for composition. Function composition is discussed further in Recipe 5.8. The peek method in Stream is examined in Recipe 3.5.

2.2 Suppliers

Problem

You want to implement the java.util.function.Supplier interface.

Solution

Implement the T get() method in java.util.function.Supplier using a lambda expression or a method reference.

Discussion

The java.util.function.Supplier interface is particularly simple. It does not have any static or default methods. It contains only a single, abstract method, T get().

Implementing Supplier means providing a method that takes no arguments and returns the generic type. As stated in the Javadocs, there is no requirement that a new or distinct result be returned each time the Supplier is invoked.

One simple example of a Supplier is the Math.random method, which takes no arguments and returns a double. That can be assigned to a Supplier reference and invoked at any time, as in Example 2-4.

Example 2-4. Using Math.random() as a Supplier
Logger logger = Logger.getLogger("...");

DoubleSupplier randomSupplier = new DoubleSupplier() { 1
    @Override
    public double getAsDouble() {
        return Math.random();
    }
};

randomSupplier = () -> Math.random();                  2
randomSupplier = Math::random;                         3

logger.info(randomSupplier);
1

Anonymous inner class implementation

2

Expression lambda

3

Method reference

The single abstract method in DoubleSupplier is getAsDouble, which returns a dou⁠ble. The other associated Supplier interfaces in the java.util.function package are shown in Table 2-2.

Table 2-2. Additional Supplier interfaces
Interface Single abstract method

IntSupplier

int getAsInt()

DoubleSupplier

double getAsDouble()

LongSupplier

long getAsLong()

BooleanSupplier

boolean getAsBoolean()

One of the primary use cases for Suppliers is to support the concept of deferred execution. The info method in java.util.logging.Logger takes a Supplier, whose get method is only called if the log level means the message will be seen (shown in detail in Recipe 5.7). This process of deferred execution can be used in your own code, to ensure that a value is retrieved from a Supplier only when appropriate.

Another example from the standard library is the orElseGet method in Optional, which also takes a Supplier. The Optional class is discussed in Chapter 6, but the short explanation is that an Optional is a nonnull object that either wraps a value or is empty. It is typically returned by methods that may reasonably expect to have no result, like finding a value in an empty collection.

To see how that might work, consider searching for a name in a collection, as shown in Example 2-5.

Example 2-5. Finding a name from a collection
List<String> names = Arrays.asList("Mal", "Wash", "Kaylee", "Inara",
    "Zoë", "Jayne", "Simon", "River", "Shepherd Book");

Optional<String> first = names.stream()
    .filter(name -> name.startsWith("C"))
    .findFirst();

System.out.println(first);                1
System.out.println(first.orElse("None")); 2

System.out.println(first.orElse(String.format("No result found in %s",
    names.stream().collect(Collectors.joining(", "))))); 3

System.out.println(first.orElseGet(() ->
    String.format("No result found in %s",
    names.stream().collect(Collectors.joining(", "))))); 4
1

Prints Optional.empty

2

Prints the string "None"

3

Forms the comma-separated collection, even when name is found

4

Forms the comma-separated collection only if the Optional is empty

The findFirst method on Stream returns the first encountered element in an ordered stream.2 Since it’s possible to apply a filter so there are no elements remaining in the stream, the method returns an Optional. That Optional either contains the desired element, or is empty. In this case, none of the names in the list pass the filter, so the result is an empty Optional.

The orElse method on Optional returns either the contained element, or a specified default. That’s fine if the default is a simple string, but can be wasteful if processing is necessary to return a value.

In this case, the returned value shows the complete list of names in comma-separated form. The orElse method creates the complete string, whether the Optional contains a value or not.

The orElseGet method, however, takes a Supplier as an argument. The advantage is that the get method on the Supplier will only be invoked when the Optional is empty, so the complete name string is not formed unless it is necessary.

Other examples from the standard library that use Suppliers include:

  • The orElseThrow method in Optional, which takes a Supplier<X extends Exception>. The Supplier is only executed if an exception occurs.

  • Objects.requireNonNull(T obj, Supplier<String> messageSupplier) only customizes its response if the first argument is null.

  • CompletableFuture.supplyAsync(Supplier<U> supplier) returns a Compl⁠etable​Future that is asynchronously completed by a task running with the value obtained by calling the given Supplier.

  • The Logger class has overloads for all its logging methods that takes a Supp⁠lier​<String> rather than just a string (used as an example in Recipe 5.7).

See Also

Using the overloaded logging methods that take a Supplier is discussed in Recipe 5.7. Finding the first element in a collection is discussed in Recipe 3.9. Completable futures are part of several recipes in Chapter 9, and Optional is the topic of recipes in Chapter 6.

2.3 Predicates

Problem

You want to filter data using the java.util.function.Predicate interface.

Solution

Implement the boolean test(T t) method in the Predicate interface using a lambda expression or a method reference.

Discussion

Predicates are used primarily to filter streams. Given a stream of items, the filter method in java.util.stream.Stream takes a Predicate and returns a new stream that includes only the items that satisfy the given predicate.

The single abstract method in Predicate is boolean test(T t), which takes a single generic argument and returns true or false. The complete set of methods in Pred⁠icate, including state and defaults, is given in Example 2-6.

Example 2-6. Methods in java.util.function.Predicate
default    Predicate<T> and(Predicate<? super T> other)
static <T> Predicate<T> isEqual(Object targetRef)
default    Predicate<T> negate()
default    Predicate<T> or(Predicate<? super T> other)
boolean    test(T t)  1
1

Single abstract method

Say you have a collection of names and you want to find all the instances that have a particular length. Example 2-7 shows an example of how to use stream processing to do so.

Example 2-7. Finding strings of a given length
public String getNamesOfLength(int length, String... names) {
    return Arrays.stream(names)
        .filter(s -> s.length() == length)   1
        .collect(Collectors.joining(", "));
}
1

Predicate for strings of given length only

Alternatively, perhaps you want only the names that start with a particular string, as in Example 2-8.

Example 2-8. Finding strings that start with a given string
public String getNamesStartingWith(String s, String... names) {
    return Arrays.stream(names)
        .filter(str -> str.startsWith(s)) 1
        .collect(Collectors.joining(", "));
}
1

Predicate to return strings starting with a given string

These can be made more general by allowing the condition to be specified by the client. Example 2-9 shows a method to do that.

Example 2-9. Finding strings that satisfy an arbitrary predicate
public class ImplementPredicate {
    public String getNamesSatisfyingCondition(
        Predicate<String> condition, String... names) {
            return Arrays.stream(names)
                .filter(condition)  1
                .collect(Collectors.joining(", "));
        }
    }

    // ... other methods ...
}
1

Filter by supplied predicate

This is quite flexible, but it may be a bit much to expect the clients to write every predicate themselves. One option is to add constants to the class representing the most common cases, as in Example 2-10.

Example 2-10. Adding constants for common cases
public class ImplementPredicate {
    public static final Predicate<String> LENGTH_FIVE = s -> s.length() == 5;
    public static final Predicate<String> STARTS_WITH_S =
        s -> s.startsWith("S");

    // ... rest as before ...
}

The other advantage to supplying a predicate as an argument is that you can also use the default methods and, or, and negate to create a composite predicate from a series of individual elements.

The test case in Example 2-11 demonstrates all of these techniques.

Example 2-11. JUnit test for predicate methods
import static functionpackage.ImplementPredicate.*; 1
import static org.junit.Assert.assertEquals;

// ... other imports ...

public class ImplementPredicateTest {
    private ImplementPredicate demo = new ImplementPredicate();
    private String[] names;

    @Before
    public void setUp() {
        names = Stream.of("Mal", "Wash", "Kaylee", "Inara", "Zoë",
            "Jayne", "Simon", "River", "Shepherd Book")
            .sorted()
            .toArray(String[]::new);
    }

    @Test
    public void getNamesOfLength5() throws Exception {
        assertEquals("Inara, Jayne, River, Simon",
            demo.getNamesOfLength(5, names));
    }

    @Test
    public void getNamesStartingWithS() throws Exception {
        assertEquals("Shepherd Book, Simon",
            demo.getNamesStartingWith("S", names));
    }

    @Test
    public void getNamesSatisfyingCondition() throws Exception {
        assertEquals("Inara, Jayne, River, Simon",
            demo.getNamesSatisfyingCondition(s -> s.length() == 5, names));
        assertEquals("Shepherd Book, Simon",
            demo.getNamesSatisfyingCondition(s -> s.startsWith("S"),
            names));
        assertEquals("Inara, Jayne, River, Simon",
            demo.getNamesSatisfyingCondition(LENGTH_FIVE, names));
        assertEquals("Shepherd Book, Simon",
            demo.getNamesSatisfyingCondition(STARTS_WITH_S, names));
    }

    @Test
    public void composedPredicate() throws Exception {
        assertEquals("Simon",
            demo.getNamesSatisfyingCondition(
                LENGTH_FIVE.and(STARTS_WITH_S), names));  2
        assertEquals("Inara, Jayne, River, Shepherd Book, Simon",
            demo.getNamesSatisfyingCondition(
                LENGTH_FIVE.or(STARTS_WITH_S), names));   2
        assertEquals("Kaylee, Mal, Shepherd Book, Wash, Zoë",
            demo.getNamesSatisfyingCondition(LENGTH_FIVE.negate(), names)); 3
    }
}
1

Static import to make using constants simpler

2

Composition

3

Negation

Other methods in the standard library that use predicates include:

Optional.filter(Predicate<? super T> predicate)

If a value is present, and the value matches the given predicate, returns an Optional describing the value, otherwise returns an empty Optional.

Collection.removeIf(Predicate<? super E> filter)

Removes all elements of this collection that satisfy the predicate.

Stream.allMatch(Predicate<? super T> predicate)

Returns true if all elements of the stream satisfy the given predicate. The methods anyMatch and noneMatch work similarly.

Collectors.partitioningBy(Predicate<? super T> predicate)

Returns a Collector that splits a stream into two categories: those that satisfy the predicate and those that do not.

Predicates are useful whenever a stream should only return certain elements. This recipe hopefully gives you an idea where and when that might be useful.

See Also

Closure composition is also discussed in Recipe 5.8. The allMatch, anyMatch, and noneMatch methods are discussed in Recipe 3.10. Partitioning and group by operations are discussed in Recipe 4.5.

2.4 Functions

Problem

You need to implement the java.util.function.Function interface to transform an input parameter into an output value.

Solution

Provide a lambda expression that implements the R apply(T t) method.

Discussion

The functional interface java.util.function.Function contains the single abstract method apply, which is invoked to transform a generic input parameter of type T into a generic output value of type R. The methods in Function are shown in Example 2-12.

Example 2-12. Methods in the java.util.function.Function interface
default <V> Function<T,V> andThen(Function<? super R,? extends V> after)
            R             apply(T t)
default <V> Function<V,R> compose(Function<? super V,? extends T> before)
static  <T> Function<T,T> identity()

The most common usage of Function is as an argument to the Stream.map method. For example, one way to transform a String into an integer would be to invoke the length method on each instance, as in Example 2-13.

Example 2-13. Mapping strings to their lengths
List<String> names = Arrays.asList("Mal", "Wash", "Kaylee", "Inara",
        "Zoë", "Jayne", "Simon", "River", "Shepherd Book");

List<Integer> nameLengths = names.stream()
        .map(new Function<String, Integer>() { 1
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        })
        .collect(Collectors.toList());

nameLengths = names.stream()
        .map(s -> s.length())                  2
        .collect(Collectors.toList());

nameLengths = names.stream()
        .map(String::length)                   3
        .collect(Collectors.toList());

System.out.printf("nameLengths = %s%n", nameLengths);
// nameLengths == [3, 4, 6, 5, 3, 5, 5, 5, 13]
1

Anonymous inner class

2

Lambda expression

3

Method reference

The complete list of primitive variations for both the input and the output generic types are shown in Table 2-3.

Table 2-3. Additional Function interfaces
Interface Single abstract method

IntFunction

R apply(int value)

DoubleFunction

R apply(double value)

LongFunction

R apply(long value)

ToIntFunction

int applyAsInt(T value)

ToDoubleFunction

double applyAsDouble(T value)

ToLongFunction

long applyAsLong(T value)

DoubleToIntFunction

int applyAsInt(double value)

DoubleToLongFunction

long applyAsLong(double value)

IntToDoubleFunction

double applyAsDouble(int value)

IntToLongFunction

long applyAsLong(int value)

LongToDoubleFunction

double applyAsDouble(long value)

LongToIntFunction

int applyAsInt(long value)

BiFunction

R apply(T t, U u)

The argument to the map method in Example 2-13 could have been a ToIntFunction, because the return type on the method is an int primitive. The Stream.mapToInt method takes a ToIntFunction as an argument, and mapToDouble and mapToLong are analogous. The return types on mapToInt, mapToDouble, and mapToLong are IntStream, DoubleStream, and LongStream, respectively.

What if the argument and return type are the same? The java.util.function package defines UnaryOperator for that. As you might expect, there are also interfaces called IntUnaryOperator, DoubleUnaryOperator, and LongUnaryOperator, where the input and output arguments are int, double, and long, respectively. An example of a UnaryOperator would be the reverse method in StringBuilder, because both the input type and the output type are strings.

The BiFunction interface is defined for two generic input types and one generic output type, all of which are assumed to be different. If all three are the same, the package includes the BinaryOperator interface. An example of a binary operator would be Math.max, because both inputs and the output are either int, double, float, or long. Of course, the interface also defines interfaces called IntBinaryOperator, Dou⁠ble​BinaryOperator, and LongBinaryOperator for those situations.3

To complete the set, the package also has primitive variations of BiFunction, which are summarized in Table 2-4.

Table 2-4. Additional BiFunction interfaces
Interface Single abstract method

ToIntBiFunction

int applyAsInt(T t, U u)

ToDoubleBiFunction

double applyAsDouble(T t, U u)

ToLongBiFunction

long applyAsLong(T t, U u)

While the various Stream.map methods are the primary usages of Function, they do appear in other contexts. Among them are:

Map.computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)

If the specified key does not have a value, use the provided Function to compute one and add it to a Map.

Comparator.comparing(Function<? super T,? extends U> keyExtractor)

Discussed in Recipe 4.1, this method generates a Comparator that sorts a collection by the key generated from the given Function.

Comparator.thenComparing(Function<? super T,? extends U> keyExtractor)

An instance method, also used in sorting, that adds an additional sorting mechanism if the collection has equal values by the first sort.

Functions are also used extensively in the Collectors utility class for grouping and downstream collectors.

The andThen and compose methods are discussed in Recipe 5.8. The identity method is simply the lambda expression e -> e. One usage is shown in Recipe 4.3.

See Also

See Recipe 5.8 for examples of the andThen and compose methods in the Function interface. See Recipe 4.3 for an example of Function.identity. See Recipe 4.6 for examples of using functions as downstream collectors. The computeIfAbsent method is discussed in Recipe 5.4. Binary operators are also covered in Recipe 3.3.

1 This is such a common operation that forEach was also added directly to Iterable. The Stream variation is useful when the source elements do not come from a collection, or if you want to make the stream parallel.

2 Streams may have an encounter order or they may not, just as lists are assumed to be ordered by index and sets are not. This can be different from the order in which elements are processed. See Recipe 3.9 for more information.

3 See Recipe 3.3 for more on BinaryOperator uses in the standard library.