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 DoubleConsumer) 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.
The java.util.function.Consumer interface has as its single, abstract method, void accept(T t). See Example 2-1.
voidaccept(Tt)defaultConsumer<T>andThen(Consumer<?superT>after)
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.
All linear collections implement this interface by performing the given action for each element of the collection, as in Example 2-3.
List<String>strings=Arrays.asList("this","is","a","list","of","strings");strings.forEach(newConsumer<String>(){@Overridepublicvoidaccept(Strings){System.out.println(s);}});strings.forEach(s->System.out.println(s));strings.forEach(System.out::println);
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.
| Interface | Single abstract method |
|---|---|
|
|
|
|
|
|
|
|
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. ObjLongConsumer 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).
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.
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.
Loggerlogger=Logger.getLogger("...");DoubleSupplierrandomSupplier=newDoubleSupplier(){@OverridepublicdoublegetAsDouble(){returnMath.random();}};randomSupplier=()->Math.random();randomSupplier=Math::random;logger.info(randomSupplier);
The single abstract method in DoubleSupplier is getAsDouble, which returns a double. The other associated Supplier interfaces in the java.util.function package are shown in Table 2-2.
| Interface | Single abstract method |
|---|---|
|
|
|
|
|
|
|
|
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.
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);System.out.println(first.orElse("None"));System.out.println(first.orElse(String.format("No result found in %s",names.stream().collect(Collectors.joining(", ")))));System.out.println(first.orElseGet(()->String.format("No result found in %s",names.stream().collect(Collectors.joining(", ")))));
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 CompletableFuture 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 Supplier<String> rather than just a string (used as an example in Recipe 5.7).
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.
Implement the boolean test(T t) method in the Predicate interface using a lambda expression or a method reference.
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 Predicate, including state and defaults, is given in Example 2-6.
defaultPredicate<T>and(Predicate<?superT>other)static<T>Predicate<T>isEqual(ObjecttargetRef)defaultPredicate<T>negate()defaultPredicate<T>or(Predicate<?superT>other)booleantest(Tt)
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.
publicStringgetNamesOfLength(intlength,String...names){returnArrays.stream(names).filter(s->s.length()==length).collect(Collectors.joining(", "));}
Alternatively, perhaps you want only the names that start with a particular string, as in Example 2-8.
publicStringgetNamesStartingWith(Strings,String...names){returnArrays.stream(names).filter(str->str.startsWith(s)).collect(Collectors.joining(", "));}
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.
publicclassImplementPredicate{publicStringgetNamesSatisfyingCondition(Predicate<String>condition,String...names){returnArrays.stream(names).filter(condition).collect(Collectors.joining(", "));}}// ... other methods ...}
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.
publicclassImplementPredicate{publicstaticfinalPredicate<String>LENGTH_FIVE=s->s.length()==5;publicstaticfinalPredicate<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.
importstaticfunctionpackage.ImplementPredicate.*;importstaticorg.junit.Assert.assertEquals;// ... other imports ...publicclassImplementPredicateTest{privateImplementPredicatedemo=newImplementPredicate();privateString[]names;@BeforepublicvoidsetUp(){names=Stream.of("Mal","Wash","Kaylee","Inara","Zoë","Jayne","Simon","River","Shepherd Book").sorted().toArray(String[]::new);}@TestpublicvoidgetNamesOfLength5()throwsException{assertEquals("Inara, Jayne, River, Simon",demo.getNamesOfLength(5,names));}@TestpublicvoidgetNamesStartingWithS()throwsException{assertEquals("Shepherd Book, Simon",demo.getNamesStartingWith("S",names));}@TestpublicvoidgetNamesSatisfyingCondition()throwsException{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));}@TestpublicvoidcomposedPredicate()throwsException{assertEquals("Simon",demo.getNamesSatisfyingCondition(LENGTH_FIVE.and(STARTS_WITH_S),names));assertEquals("Inara, Jayne, River, Shepherd Book, Simon",demo.getNamesSatisfyingCondition(LENGTH_FIVE.or(STARTS_WITH_S),names));assertEquals("Kaylee, Mal, Shepherd Book, Wash, Zoë",demo.getNamesSatisfyingCondition(LENGTH_FIVE.negate(),names));}}
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.
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.
Provide a lambda expression that implements the R apply(T t) method.
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.
default<V>Function<T,V>andThen(Function<?superR,?extendsV>after)Rapply(Tt)default<V>Function<V,R>compose(Function<?superV,?extendsT>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.
List<String>names=Arrays.asList("Mal","Wash","Kaylee","Inara","Zoë","Jayne","Simon","River","Shepherd Book");List<Integer>nameLengths=names.stream().map(newFunction<String,Integer>(){@OverridepublicIntegerapply(Strings){returns.length();}}).collect(Collectors.toList());nameLengths=names.stream().map(s->s.length()).collect(Collectors.toList());nameLengths=names.stream().map(String::length).collect(Collectors.toList());System.out.printf("nameLengths = %s%n",nameLengths);// nameLengths == [3, 4, 6, 5, 3, 5, 5, 5, 13]
The complete list of primitive variations for both the input and the output generic types are shown in Table 2-3.
| Interface | Single abstract method |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, DoubleBinaryOperator, 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.
| Interface | Single abstract method |
|---|---|
|
|
|
|
|
|
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 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.