Chapter 5. Issues with Streams, Lambdas, and Method References

Now that you know the basics of lambdas and method references and how they are used in streams, there are several topics that arise from the combination. For example, now that interfaces can have default methods, what happens when a class implements multiple interfaces that have the same default method signature but different implementations? As another example, what happens when you are writing code in a lambda expression and try to access or modify a variable defined outside it? Also, what about exceptions? How are they handled in lambda expressions, where you have no method signature on which to add a throws clause?

This chapter deals with all these issues and more.

5.1 The java.util.Objects Class

Problem

You wish to use static utility methods for null checking, comparisons, and more.

Solution

Use the java.util.Objects class, added in Java 7, but helpful during stream processing.

Discussion

One of the lesser-known classes added in Java 7 is the java.util.Objects class, which contains static methods for a variety of tasks. These methods include:

static boolean deepEquals(Object a, Object b)

Checks for “deep” equality, which is particularly useful when comparing arrays.

static boolean equals(Object a, Object b)

Uses the equals method from the first argument, but is null safe.

static int hash(Object... values)

Generates a hash code for a sequence of input values.

static String toString(Object o)

Returns the result of calling toString on the argument if not null, and returns null otherwise.

static String toString(Object o, String nullDefault)

Returns the result of calling toString on the first argument, and returns the second argument if the first argument is null.

There are also a few overloads of a method useful for validation of arguments:

static <T> T requireNotNull(T obj)

Returns T if not null and throws a NullPointerException (NPE) otherwise.

static <T> T requireNotNull(T obj, String message)

Same as previous method, but the NPE resulting from a null argument has the specified message.

static <T> T requireNotNull(T obj, Supplier<String> messageSupplier)

Same as previous method, but invokes the given Supplier to generate a message for the NPE if the first argument is null.

That last method takes a Supplier<String> as an argument, which finally gives a reason for including this class in a book focused on Java 8 and above. An arguably better reason, however, is given by the isNull and nonNull methods. Each of those returns a boolean:

static boolean isNull(Object obj)

Returns true if the provided reference is null and false otherwise.

static boolean nonNull(Object obj)

Returns true if the provided reference is not null and false otherwise.

The beauty of these methods is that they can be used as Predicate instances in a filter.

For example, say you have a class that returns a collection. Example 5-1 has a method to return the complete collection, whatever it may be, and a method to return the collection without any nulls.

Example 5-1. Returning a collection and filtering out nulls
List<String> strings = Arrays.asList(
    "this", null, "is", "a", null, "list", "of", "strings", null);

List<String> nonNullStrings = strings.stream()
    .filter(Objects::nonNull)       1
    .collect(Collectors.toList());
1

Filter out null elements

You can use the Objects.deepEquals method to test this, as in Example 5-2.

Example 5-2. Testing the filter
@Test
public void testNonNulls() throws Exception {
    List<String> strings =
        Arrays.asList("this", "is", "a", "list", "of", "strings");
    assertTrue(Objects.deepEquals(strings, nonNullStrings);
}

This process can be generalized so that it doesn’t just apply to strings. The code in Example 5-3 filters nulls out of any list.

Example 5-3. Filtering nulls from a generic list
public <T> List<T> getNonNullElements(List<T> list) {
    return list.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
}

Now a method that produces a List with multiple elements being null can be filtered with ease.

5.2 Lambdas and Effectively Final

Problem

Inside a lambda expression you want to access a variable defined outside it.

Solution

Local variables accessed inside lambda expressions must be final or “effectively final.” Attributes can be both accessed and modified.

Discussion

Back in the late ’90s, when Java was still shiny and new, occasionally developers would write client-side Java applications using the Swing user interface library. Like all GUI libraries, Swing components are event-driven: components generate events and listeners react to them.

Since it was considered a good practice to write separate listeners for each component, listeners were often implemented as anonymous inner classes. This kept them modular, but using inner classes had an added benefit: code in inner classes can access and modify the private attributes of the outer class. For example, a JButton instance generates an ActionEvent, and a ActionListener interface contains a single method called actionPerformed that is invoked once an implementation is registered as a listener. See Example 5-4 for a sample.

Example 5-4. A trivial Swing user interface
public class MyGUI extends JFrame {
    private JTextField name = new JTextField("Please enter your name");
    private JTextField response = new JTextField("Greeting");
    private JButton button = new JButton("Say Hi");

    public MyGUI() {
        // ... unrelated GUI setup code ...
        String greeting = "Hello, %s!";                     1
        button.addActionListener(new ActionListener() {
           @Override
           public void actionPerformed(ActionEvent e) {
              response.setText(
                  String.format(greeting, name.getText());  2
              // greeting = "Anything else";                3
           }
        });
    }
}
1

Local variable

2

Access local variable and attributes

3

Modify local variable (will not compile)

The greeting string is a local variable defined inside the constructor. The name and response variables are attributes of the class. The ActionListener interface is implemented as an anonymous inner class, whose one method is actionPerformed. Inside the inner class, code can:

  • Access attributes like name and response

  • Modify attributes (though that’s not shown here)

  • Access the local variable greeting

  • Cannot modify the local variable

In fact, prior to Java 8, the compiler would have required the greeting variable to be declared final. In Java 8, the variable doesn’t have to be declared final, but it must be effectively final. In other words, any code that tries to change the value of a local variable will not compile.

Of course, in Java 8, the anonymous inner class would be replaced by a lambda expression, as in Example 5-5.

Example 5-5. Lambda expression for the listener
String greeting = "Hello, %s!";
button.addActionListener(e ->
    response.setText(String.format(greeting,name.getText())));

The same rules apply. The greeting variable doesn’t have to be declared to be final, but it must be effectively final or the code will not compile.

If Swing isn’t to your liking, here’s another way to approach this problem. Say you want to sum values in a given List, as in Example 5-6.

Example 5-6. Sum the values in a List
List<Integer> nums = Arrays.asList(3, 1, 4, 1, 5, 9);

int total = 0;                           1
for (int n : nums) {                     2
    total += n;
}

total = 0;
nums.forEach(n -> total += n);           3

total = nums.stream()                    4
            .mapToInt(Integer::valueOf)
            .sum()
1

Local variable total

2

Traditional for-each loop

3

Modify local variable in a lambda: WILL NOT COMPILE

4

Convert stream to IntStream and call sum

The code declares a local variable called total. Summing the values using a traditional for-each loop works just fine.

The forEach method defined on Iterable takes a Consumer as an argument, but if the consumer tries to modify the total variable, the code will not compile.

Of course, the right way to solve the problem is to convert the stream into an IntStream, which then has a sum method—no local variables involved at all.

Technically, a function along with the accessible variables defined in its environment is called a closure. By that definition, Java is in somewhat of a gray area—local variables are accessible but cannot be modified. You could argue that Java 8 lambdas are actually closures, in that they are closed over values rather than variables.1

See Also

Other languages have different rules for closure variables. For example, Groovy allows you to modify them, though that is not considered a good practice.

5.3 Streams of Random Numbers

Problem

You want a stream of random integers, longs, or doubles, within a given set of bounds.

Solution

Use the static ints, longs, and doubles methods in java.util.Random.

Discussion

If all you need is a single random double, the static Math.random method is convenient. It returns a double value between 0.0 and 1.0.2 The process is equivalent to instantiating the java.util.Random class and invoking the nextDouble method.

The Random class provides a constructor that lets you specify a seed. If you specify the same seed, you get the same resulting sequence of random numbers, which can be useful in testing.

If you want a sequential stream of random numbers, however, Java 8 added several methods to the Random class that return them. The methods are ints, longs, and doub⁠les, whose signatures are (without the various overloads):

IntStream    ints()
LongStream   longs()
DoubleStream doubles()

The overloaded versions of each allow you to specify the size of the resulting stream and the min and max values for the generated numbers. For example:

DoubleStream doubles(long streamSize, double randomNumberOrigin,
    double randomNumberBound)

The returned stream produces the given streamSize number of pseudorandom double values, each of which is greater than or equal to the randomNumberOrigin and strictly less than the randomNumberBound.

Variants that don’t specify streamSize return an “effectively unlimited” stream of values.

If you don’t specify the min or max, they default to zero and one for doubles, the complete range of Integer for ints, and (effectively) the complete range of Long for longs. In each case, the result is like repeatedly invoking nextDouble, nextInt, or nextLong, respectively.

Example 5-7 shows the sample code.

Example 5-7. Generating streams of random numbers
Random r = new Random();
r.ints(5)                           1
 .sorted()
 .forEach(System.out::println);

r.doubles(5, 0, 0.5)                2
 .sorted()
 .forEach(System.out::println);

List<Long> longs = r.longs(5)
                    .boxed()        3
                    .collect(Collectors.toList());
System.out.println(longs);

List<Integer> listOfInts = r.ints(5, 10, 20)
    .collect(LinkedList::new, LinkedList::add, LinkedList::addAll); 4
System.out.println(listOfInts);
1

Five random integers

2

Five random doubles between 0 (inclusive) and 0.5 (exclusive)

3

Boxing long to Long so they can be collected

4

Alternative form of collect instead of calling boxed

The latter two examples deal with the minor annoyance that occurs when you want to create a collection of primitives. You can’t invoke collect(Collectors.toList()) on a collection of primitives, as discussed in Recipe 3.2. As suggested in that recipe, you can either use the boxed method to convert the long values to instances of Long, or you can use the three-argument version of collect and specify the Supplier, accumulator, and combiner yourself, as shown.

It’s worth noting that SecureRandom is a subclass of Random. It provides a cryptographically strong random number generator. All the same methods (ints, longs, doubles, and their overloads) also work on SecureRandom, just with a different generator.

See Also

The boxed method in Stream is discussed in Recipe 3.2.

5.4 Default Methods in Map

Problem

You want to add or replace elements in a Map only if they already exist or are absent, or other related operations.

Solution

Use one of the many new default methods in the java.util.Map interface, like computeIfAbsent, computeIfPresent, replace, merge, and so on.

Discussion

The Map interface has been in Java since the rest of the collections framework was added way back in version 1.2. Java 8 introduced default methods in interfaces, and several new methods have been added to Map as a result.

Table 5-1 shows the new methods.

Table 5-1. Default methods in Map
Method Purpose

compute

Compute a new value based on the existing key and value

computeIfAbsent

Return the value for the given key if it exists, or use the supplied function to compute and store it if not

computeIfPresent

Compute a new value to replace an existing value

forEach

Iterate over a Map, passing each key and value to the consumer

getOrDefault

If the key exists in the Map, return its value; otherwise return the default

merge

If the key is not in the Map, return the supplied value; otherwise compute a new value

putIfAbsent

If the key isn’t in the Map, associate it with the given value

remove

Remove the entry for this key only if it matches the given value

replace

Replace the existing key with the new value

replaceAll

Replace each entry in the Map with the result of applying the given function to the current entry

That’s a lot of new methods for an interface we’ve been using for over a decade. Some of them are really convenient, however.

computeIfAbsent

The complete signature for the computIfAbsent method is:

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

This method is particularly useful when creating a cache of the results of method calls. For example, consider the classic recursive calculation of Fibonacci numbers. The value of any Fibonacci number greater than 1 is equal to the sum of the previous two Fibonacci numbers,3 as in Example 5-8.

Example 5-8. Recursive calculation of Fibonacci numbers
long fib(long i) {
    if (i == 0) return 0;
    if (i == 1) return 1;
    return fib(i - 1) + fib(i - 2);  1
}
1

Highly inefficient

The problem is that fib(5) = fib(4) + fib(3) = fib(3) + fib(2) + fib(2) + fib(1) = ... and there are many, many repeated calculations. The way to fix this is to use a cache, a technique known as memoization in functional programming. The result, modified to store BigInteger instances, is shown in Example 5-9.

Example 5-9. Fibonacci calculation with a cache
private Map<Long, BigInteger> cache = new HashMap<>();

public BigInteger fib(long i) {
    if (i == 0) return BigInteger.ZERO;
    if (i == 1) return BigInteger.ONE;

    return cache.computeIfAbsent(i, n -> fib(n - 2).add(fib(n - 1))); 1
}
1

Cache returns value if it exists, or computes and stores it if not

The calculation uses a cache where the keys are the supplied numbers and the values are the corresponding Fibonacci numbers. The computeIfAbsent method looks in the cache for a given number. If it exists, it returns the value. Otherwise it uses the supplied Function to compute the new value, store it in the cache, and return it. That’s quite an improvement for a single method.

computeIfPresent

The complete signature of the computeIfPresent method is:

V computeIfPresent(K key,
    BiFunction<? super K, ? super V, ? extends V> remappingFunction)

The computeIfPresent method only updates a value if its associated key is already in the map. Consider the case where you are parsing text and making a count of how many times each word appears. Such a calculation, known as a concordance, is not uncommon. If, however, you only care about certain specific keywords, you can use computeIfPresent to update those. See Example 5-10.

Example 5-10. Update the word counts only for specific words
public Map<String,Integer> countWords(String passage, String... strings) {
    Map<String, Integer> wordCounts = new HashMap<>();

    Arrays.stream(strings).forEach(s -> wordCounts.put(s, 0)); 1

    Arrays.stream(passage.split(" ")).forEach(word ->          2
        wordCounts.computeIfPresent(word, (key, val) -> val + 1));

    return wordCounts;
}
1

Put the words we care about in the map with a count of zero

2

Read the passage, updating the counts only for the words we care about

By putting the words you care about into the map initially with a count of zero, the computeIfPresent method will only update those values.

If you run this with a passage of text and a comma-separated list of words, as in Example 5-11, you get the results you’re looking for.

Example 5-11. Calling the countWords method
String passage = "NSA agent walks into a bar. Bartender says, " +
    "'Hey, I have a new joke for you.' NSA agent says, 'heard it'.";

Map<String, Integer> counts = demo.countWords(passage, "NSA", "agent", "joke");
counts.forEach((word, count) -> System.out.println(word + "=" + count));

// Output is NSA=2, agent=2, joke=1

Only the desired words are keys in the map, so only those have their counts updated. As usual, printing the values takes advantage of the default forEach method in Map, which takes a BiConsumer, whose arguments are the keys and the values.

Other methods

The replace method works like the put method, but only if the key already exists. If not, the replace method does nothing, while put adds a null key, which may not be what you want.

There are two overloads of the replace method:

V replace(K key, V value)
boolean replace(K key, V oldValue, V newValue)

The first one replaces the value of the key if it exists in the map at all. The second only does the replacement if the current value is the one specified.

The getOrDefault method solves the occasionally annoying fact that calling get on a Map with a key that doesn’t exist returns null. That’s helpful, but the method only returns the default, it doesn’t also add it to the map.

The signature of getOrDefault is:

V getOrDefault(Object key, V defaultValue)
Warning

The getOrDefault method returns the default if the key does not exist in the map, but it does not add the key to the map.

The merge method is very helpful. Its complete signature is:

V merge(K key, V value,
            BiFunction<? super V, ? super V, ? extends V> remappingFunction)

Say you want the complete word count map for a given passage of text, rather than just the counts for specific words. Normally you would have two conditions: if the word is already in the map, update the count; otherwise put it in the map with a count of one. With merge the process simplifies, as in Example 5-12.

Example 5-12. Using the merge method
public Map<String, Integer> fullWordCounts(String passage) {
    Map<String, Integer> wordCounts = new HashMap<>();
    String testString = passage.toLowerCase().replaceAll("\\W"," "); 1

    Arrays.stream(testString.split("\\s+")).forEach(word ->
        wordCounts.merge(word, 1, Integer::sum));                    2

    return wordCounts;
}
1

Remove case sensitivity and punctuation

2

Add or update the count for a given word

The merge method takes the key and default value, which is inserted if the key doesn’t already exist in the map. Otherwise merge uses the BinaryOperator (here the sum method in Integer) to compute the new value based on the old value.

Hopefully this recipe makes it clear that the new default methods on Map provide several convenient techniques for your coding.

5.5 Default Method Conflict

Problem

You have a class that implements two interfaces, each of which contains the same default method with different implementations.

Solution

Implement the method in your class. Your implementation can still use the provided defaults from the interfaces through the super keyword.

Discussion

Java 8 supports both static and default methods in interfaces. Default methods provide an implementation, which is then inherited by the class. This allows interfaces to add new methods without breaking existing class implementations.

Since classes can implement multiple interfaces, a class may inherit default methods that have the same signature but are implemented differently, or it may already contain its own version of a default method.

There are three possibilities when this occurs:

  • In any conflict between a method in a class and a default method in an interface, the class always wins.

  • If the conflict comes between two interfaces where one is a descendant of the other, then the descendant wins, the same way they do in classes.

  • If there is no inheritance relationship between the two defaults, the class will not compile.

In the last case, simply implement the method in the class and everything will work again. This reduces the third case to the first one.

As an example, consider the Company interface shown in Example 5-13 and the Employee interface shown in Example 5-14.

Example 5-13. The Company interface with a default method
public interface Company {
    default String getName() {
        return "Initech";
    }

    // other methods
}

The default keyword indicates that the getName method is a default method, which provides an implementation that returns the company name.

Example 5-14. The Employee interface with a default method
public interface Employee {
    String getFirst();

    String getLast();

    void convertCaffeineToCodeForMoney();

    default String getName() {
        return String.format("%s %s", getFirst(), getLast());
    }
}

The Employee interface also contains a default method called getName with the same signature as the one in Company, but with a different implementation. The Comp⁠any​Employee class shown in Example 5-15 implements both interfaces, causing a conflict.

Example 5-15. First attempt at CompanyEmployee (WON’T COMPILE)
public class CompanyEmployee implements Company, Employee {
    private String first;
    private String last;

    @Override
    public void convertCaffeineToCodeForMoney() {
        System.out.println("Coding...");
    }

    @Override
    public String getFirst() {
        return first;
    }

    @Override
    public String getLast() {
        return last;
    }
}

Since CompanyEmployee inherits unrelated defaults for getName, the class won’t compile. To fix this, you need to add your own version of getName to the class, which will then override both the defaults.

You can still use the provided defaults, however, using the super keyword, as shown in Example 5-16.

Example 5-16. Fixed version of CompanyEmployee
public class CompanyEmployee implements Company, Employee {

    @Override
    public String getName() {                                   1
        return String.format("%s working for %s",
            Employee.super.getName(), Company.super.getName()); 2
    }

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

Implement getName

2

Access default implementations using super

In this version, the getName method in the class builds a String from the default versions provided by both Company and Employee.

The best news of all is that this is as complicated as default methods ever get. You now know everything there is to know about them.

Actually, there’s one edge case to consider. If the Company interface contained getName but was not marked default (and didn’t have an implementation, making it abstract), would that still cause a conflict because Employee also had the same method? The answer is yes, interestingly enough, and you still need to provide an implementation in the CompanyEmployee class.

Of course, if the same method appears in both interfaces and neither is a default, then this is the pre-Java 8 situation. There’s no conflict, but the class must provide an implementation.

See Also

Default methods in interfaces are discussed in Recipe 1.5.

5.6 Iterating Over Collections and Maps

Problem

You want to iterate over a collection or map.

Solution

Use the forEach method, which was added as a default method to both Iterable and Map.

Discussion

Rather than using a loop to iterate over a linear collection (i.e., a class that implements Collection or one of its descendants), you can use the new forEach method that has been added to Iterable as a default method.

From the Javadocs, its signature is:

default void forEach(Consumer<? super T> action)

The argument to forEach is of type Consumer, one of the functional interfaces added to the java.util.function package. A Consumer represents an operation that takes a single generic parameter and returns no result. As the docs say, “unlike most other functional interfaces, Consumer is expected to operate via side effects.”

Note

A pure function operates without side effects, so applying the function with the same parameters always gives the same result. In functional programming, this is known as referential transparency, where a function can be replaced by its corresponding value.

Since java.util.Collection is a subinterface of Iterable, the forEach method is available on all linear collections, from ArrayList to LinkedHashSet. Iterating over each is therefore simple, as Example 5-17 shows.

Example 5-17. Iterating over a linear collection
List<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);

integers.forEach(new Consumer<Integer>() {    1
    @Override
    public void accept(Integer integer) {
        System.out.println(integer);
    }
});

integers.forEach((Integer n) -> {             2
    System.out.println(n);
});

integers.forEach(n -> System.out.println(n)); 3

integers.forEach(System.out::println);        4
}
1

Anonymous inner class implementation

2

Full verbose form of a block lambda

3

Expression lambda

4

Method reference

The anonymous inner class version is shown simply as a reminder of the signature of the accept method in the Consumer interface. As the inner class shows, the accept method takes a single argument and returns void. The lambda versions shown are compatible with this. Since each of the lambda versions consists of a single call to the println method on System.out, that method can be used as a method reference, as shown in the last version.

The Map interface also has a forEach method, added as a default. In this case, the signature takes a BiConsumer:

default void forEach(BiConsumer<? super K, ? super V> action)

BiConsumer is another of the new interfaces in the java.util.function package. It represents a function that takes two generic arguments and returns void. When implemented in the forEach method in Map, the arguments become the keys and values from the Map.Entry instances in the entrySet.

That means iterating over a Map is now as easy as iterating over a List, Set, or any other linear collection. Example 5-18 shows the sample code.

Example 5-18. Iterating over a Map
Map<Long, String> map = new HashMap<>();
map.put(86L, "Don Adams (Maxwell Smart)");
map.put(99L, "Barbara Feldon");
map.put(13L, "David Ketchum");

map.forEach((num, agent) ->
    System.out.printf("Agent %d, played by %s%n", num, agent));

The output from the iteration is shown in Example 5-19.4

Example 5-19. Map iteration output
Agent 99, played by Barbara Feldon
Agent 86, played by Don Adams (Maxwell Smart)
Agent 13, played by David Ketchum

Prior to Java 8, to iterate over a Map you needed to first use the keySet or entrySet methods to acquire the Set of keys or Map.Entry instances and then iterate over that. With the new default forEach method, iteration is much simpler.

Warning

Keep in mind that there is no easy way to break out of forEach. Consider rewriting your stream processing code as a filter and/or sorted instead, followed by a findFirst.

See Also

The functional interfaces Consumer and BiConsumer are discussed in Recipe 2.1.

5.7 Logging with a Supplier

Problem

You want to create a log message, but only if the log level ensures it will be seen.

Solution

Use the new logging overloads in the Logger class that take a Supplier.

Discussion

The logging methods in java.util.logging.Logger, like info, warning, or severe, now have two overloaded versions: one that takes a single String as an argument, and one that takes a Supplier<String>.

For instance, Example 5-20 shows the signatures of the various logging methods.5

Example 5-20. Overloaded logging methods java.util.logging.Logger
void config(String msg)
void config(Supplier<String> msgSupplier)

void fine(String msg)
void fine(Supplier<String> msgSupplier)

void finer(String msg)
void finer(Supplier<String> msgSupplier)

void finest(String msg)
void finest(Supplier<String> msgSupplier)

void info(String msg)
void info(Supplier<String> msgSupplier)

void warning(String msg)
void warning(Supplier<String> msgSupplier)

void severe(String msg)
void severe(Supplier<String> msgSupplier)

For each method, the version that takes a String was part of the original API that appeared in Java 1.4. The Supplier version is new to Java 8. If you look at the implementation of the Supplier version in the standard library, you see the code shown in Example 5-21.

Example 5-21. Implementation details of the Logger class
public void info(Supplier<String> msgSupplier) {
    log(Level.INFO, msgSupplier);
}

public void log(Level level, Supplier<String> msgSupplier) {
    if (!isLoggable(level)) {                               1
        return;
    }
    LogRecord lr = new LogRecord(level, msgSupplier.get()); 2
    doLog(lr);
}
1

Return if the message will not be shown

2

Retrieve the message from the Supplier by calling get

Rather than construct a message that will never be shown, the implementation checks to see if the message will be “loggable.” If the message was provided as a simple string, it would be evaluated whether it was logged or not. The version that uses a Supplier allows the developer to put empty parentheses and an arrow in front of the message, converting it into a Supplier, which will only be invoked if the log level is appropriate. Example 5-22 shows how to use both overloads of info.

Example 5-22. Using a Supplier in the info method
private Logger logger = Logger.getLogger(this.getClass().getName());
private List<String> data = new ArrayList<>();

// ... populate list with data ...

logger.info("The data is " + data.toString());       1
logger.info(() -> "The data is " + data.toString()); 2
1

Argument always constructed

2

Argument only constructed if log level shows info messages

In this example, the message invokes the toString method on every object in the list. In the first case, the resulting string will be formed whether the program shows info messages or not. Converting the log argument to a Supplier by simply adding () -> in front of it means that the get method on the Supplier will only be invoked if the message will be used.

The technique of replacing an argument with a Supplier of the same type is known as deferred execution, and can be used in any context where object creation might be expensive.

See Also

Deferred execution is one of the primary use cases for Supplier. Suppliers are discussed in Recipe 2.2.

5.8 Closure Composition

Problem

You want to apply a series of small, independent functions consecutively.

Solution

Use the composition methods defined as defaults in the Function, Consumer, and Predicate interfaces.

Discussion

One of the benefits of functional programming is that you can create a set of small, reusable functions that you can combine to solve larger problems. To support this, the functional interfaces in the java.util.function package include methods to make composition easy.

For example, the Function interface has two default methods with the signatures shown in Example 5-23.

Example 5-23. Composition methods in java.util.function.Function
default <V> Function<V,R>	compose(Function<? super V,? extends T> before)
default <V> Function<T,V>	andThen(Function<? super R,? extends V> after)

The dummy arguments’ names in the Javadocs indicate what each method does. The compose method applies its argument before the original function, while the andThen method applies its argument after the original function.

To demonstrate this, consider the trivial example shown in Example 5-24.

Example 5-24. Using the compose and andThen methods
Function<Integer, Integer> add2  = x -> x + 2;
Function<Integer, Integer> mult3 = x -> x * 3;

Function<Integer, Integer> mult3add2  = add2.compose(mult3); 1
Function<Integer, Integer> add2mult3 = add2.andThen(mult3); 2

System.out.println("mult3add2(1): " + mult3add2.apply(1));
System.out.println("add2mult3(1): " + add2mult3.apply(1));
1

First mult3, then add2

2

First add2, then mult3

The add2 function adds 2 to its argument. The mult3 function multiplies its argument by 3. Since mult3add2 is made using compose, first the mult3 function is applied and then the add2 function, whereas for add2mult3 using the andThen function does the opposite.

The results of applying each composite function gives:

mult3add2(1): 5  // because (1 * 3) + 2 == 5
add2mult3(1): 9  // because (1 + 2) * 3 == 9

The result of the composition is a function, so this process creates new operations that can be used later. Say, for example, you receive data as part of an HTTP request, which means it is transmitted in string form. You already have a method to operate on the data, but only if it’s already a number. If this happens frequently, you can compose a function that parses the string data before applying the numerical operation. For instance, see Example 5-25.

Example 5-25. Parse an integer from a string, then add 2
Function<Integer, Integer> add2 = x -> x + 2;
Function<String, Integer> parseThenAdd2 = add2.compose(Integer::parseInt);
System.out.println(parseThenAdd2.apply("1"));
// prints 3

The new function, parseThenAdd2, invokes the static Integer.parseInt method before adding 2 to the result. Going the other way, you can define a function that invokes a toString method after a numerical operation, as in Example 5-26.

Example 5-26. Add a number, then convert to a string
Function<Integer, Integer> add2 = x -> x + 2;
Function<Integer, String> plus2toString = add2.andThen(Object::toString);
System.out.println(plus2toString.apply(1));
// prints "3"

This operation returns a function that takes an Integer argument and returns a String.

The Consumer interface also has a method used for closure composition, as shown in Example 5-27.

Example 5-27. Closure composition with consumers
default Consumer<T> andThen(Consumer<? super T> after)

The Javadocs for Consumer explain that the andThen method returns a composed Cons⁠umer that performs the original operation followed by the Consumer argument. If either operation throws an exception, it is thrown to the caller of the composed operation.

See the sample code in Example 5-28.

The example code creates two consumers—one for printing to the console, and one for logging. The composed consumer does both printing and logging for each element of the stream.

The Predicate interface has three methods that can be used to compose predicates, as shown in Example 5-29.

Example 5-29. Composition methods in the Predicate interface
default Predicate<T>	and(Predicate<? super T> other)
default Predicate<T>	negate()
default Predicate<T>	or(Predicate<? super T> other)

As you might expect the and, or, and negate methods are used to compose predicates using logical and, logical or, and logical not operations. Each returns a composed predicate.

To give a moderately interesting example, consider properties of integers. A perfect square is a number whose square root is also an integer. A triangle number counts the objects that can form an equilateral triangle.6

The code in Example 5-30 shows methods for computing perfect squares and triangle numbers, and how you can use the and method to find numbers that are both.

Example 5-30. Triangle numbers that are perfect squares
public static boolean isPerfect(int x) {      1
    return Math.sqrt(x) % 1 == 0;
}

public static boolean isTriangular(int x) {   2
    double val = (Math.sqrt(8 * x + 1) - 1) / 2;
    return val % 1 == 0;
}

// ...

IntPredicate triangular = CompositionDemo::isTriangular;
IntPredicate perfect = CompositionDemo::isPerfect;
IntPredicate both = triangular.and(perfect);

IntStream.rangeClosed(1, 10_000)
        .filter(both)
        .forEach(System.out::println);       3
1

Examples: 1, 4, 9, 16, 25, 36, 49, 64, 81, …

2

Examples: 1, 3, 6, 10, 15, 21, 28, 36, 45, …

3

Both (between 1 and 10,000): 1, 36, 1225

The composition approach can be used to build up complex operations from a small library of simple functions.7

See Also

Functions are discussed in Recipe 2.4, consumers in Recipe 2.1, and predicates in Recipe 2.3.

5.9 Using an Extracted Method for Exception Handling

Problem

Code in a lambda expression needs to throw an exception, but you do not want to clutter a block lambda with exception handling code.

Solution

Create a separate method that does the operation, handle the exception there, and invoke the extracted method in your lambda expression.

Discussion

A lambda expression is effectively the implementation of the single abstract method in a functional interface. As with anonymous inner classes, lambda expressions can only throw exceptions declared in the abstract method signature.

If the required exception is unchecked, the situation is relatively easy. The ancestor of all unchecked exceptions is java.lang.RuntimeException.8 Like any Java code, a lambda expression can throw a runtime exception without declaring it or wrapping the code in a try/catch block. The exception is then propagated to the caller.

Consider, for instance, a method that divides all elements of a collection by a constant value, as shown in Example 5-31.

Example 5-31. A lambda expression that may throw an unchecked exception
public List<Integer> div(List<Integer> values, Integer factor) {
    return values.stream()
        .map(n -> n / factor)  1
        .collect(Collectors.toList());
}
1

Can throw an ArithmeticException

Integer division will throw an ArithmeticException (an unchecked exception) if the denominator is zero.9 This will be propagated to the caller, as shown in Example 5-32.

Example 5-32. Client code
List<Integer> values = Arrays.asList(30, 10, 40, 10, 50, 90);
List<Integer> scaled = demo.div(values, 10);
System.out.println(scaled);
// prints: [3, 1, 4, 1, 5, 9]

scaled = demo.div(values, 0);
System.out.println(scaled);
// throws ArithmeticException: / by zero

The client code invokes the div method, and if the divisor is zero, the lambda expression throws an ArithmeticException. The client can add a try/catch block inside the map method in order to handle the exception, but that leads to some seriously ugly code (see Example 5-33).

Example 5-33. Lambda expression with try/catch
public List<Integer> div(List<Integer> values, Integer factor) {
    return values.stream()
        .map( n -> {
            try {
                return n / factor;
            } catch (ArithmeticException e) {
                e.printStackTrace();
            }
        })
        .collect(Collectors.toList());
}

This same process works even for checked exceptions, as long as the checked exception is declared in the functional interface.

It’s generally a good idea to keep stream processing code as simple as possible, with the goal of writing one line per intermediate operation. In this case, you can simplify the code by extracting the function inside map into a method, and the stream processing could be done by calling it, as in Example 5-34.

Example 5-34. Extracting a lambda into a method
private Integer divide(Integer value, Integer factor) {
    try {
        return value / factor;
    } catch (ArithmeticException e) { 1
        e.printStackTrace();
    }
}

public List<Integer> divUsingMethod(List<Integer> values, Integer factor) {
    return values.stream()
        .map(n -> divide(n, factor))  2
        .collect(Collectors.toList());
}
1

Handle the exception here

2

Stream code is simplified

As an aside, if the extracted method had not needed the factor value, the argument to map could have been simplified to a method reference.

The technique of extracting the lambda to a separate method has benefits as well. You can write tests for the extracted method (using reflection if the method is private), set break points in it, or any other mechanism normally associated with methods.

See Also

Lambda expressions with checked exceptions are discussed in Recipe 5.10. Using a generic wrapper method for exceptions is in Recipe 5.11.

5.10 Checked Exceptions and Lambdas

Problem

You have a lambda expression that throws a checked exception, and the abstract method in the functional interface you are implementing does not declare that exception.

Solution

Add a try/catch block to the lambda expression, or delegate to an extracted method to handle it.

Discussion

A lambda expression is effectively the implementation of the single abstract method in a functional interface. A lambda expression can therefore only throw checked exceptions declared in the signature of the abstract method.

Say you are planning to invoke a service using a URL and you need to form a query string from a collection of string parameters. The parameters need to be encoded in a way that allows them to be used in a URL. Java provides a class for this purpose called, naturally enough, java.net.URLEncoder, which has a static encode method that takes a String and encodes it according to a specified encoding scheme.

In this case, what you would like to write is code like Example 5-35.

Example 5-35. URL encoding a collection of strings (NOTE: DOES NOT COMPILE)
public List<String> encodeValues(String... values) {
    return Arrays.stream(values)
        .map(s -> URLEncoder.encode(s, "UTF-8")))  1
        .collect(Collectors.toList());
}
1

Throws UnsupportedEncodingException, which must be handled

The method takes a variable argument list of strings and tries to run each of them through the UREncoder.encode method under the recommended UTF-8 encoding. Unfortunately, the code does not compile because that method throws a (checked) Unsupported⁠Encoding​Exception.

You might be tempted to simply declare that the encodeValues method throws that exception, but that doesn’t work (see Example 5-36).

Example 5-36. Declaring the exception (ALSO DOES NOT COMPILE)
public List<String> encodeValues(String... values)
    throws UnsupportedEncodingException {  1
        return Arrays.stream(values)
            .map(s -> URLEncoder.encode(s, "UTF-8")))
            .collect(Collectors.toList());
}
1

Throwing the exception from the surrounding method also DOES NOT COMPILE

The problem is that throwing an exception from a lambda is like building an entirely separate class with a method and throwing the exception from there. It helps to think of the lambda as the implementation of an anonymous inner class, because then it becomes clear that throwing the exception in the inner object still needs to be handled or declared there, not in the surrounding object. Code like that is shown in Example 5-37, which shows both the anonymous inner class version and the lambda expression version.

Example 5-37. URL encoding using try/catch (CORRECT)
public List<String> encodeValuesAnonInnerClass(String... values) {
    return Arrays.stream(values)
        .map(new Function<String, String>() {          1
            @Override
            public String apply(String s) {            2
                try {
                    return URLEncoder.encode(s, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    return "";
                }
            }
        })
        .collect(Collectors.toList());
}

public List<String> encodeValues(String... values) {   3
    return Arrays.stream(values)
        .map(s -> {
            try {
                return URLEncoder.encode(s, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return "";
            }
        })
        .collect(Collectors.toList());
}
1

Anonymous inner class

2

Contains code that will throw a checked exception

3

Lambda expression version

Since the apply method (the single abstract method from Function) does not declare any checked exceptions, you must add a try/catch block inside any lambda expression that is implementing it. If you use a lambda expression as shown, you don’t even see the apply method signature at all, even if you wanted to modify it (which isn’t allowed anyway).

Example 5-38 shows a version that uses an extracted method for the encoding.

Example 5-38. URL encoding delegating to a method
private String encodeString(String s) { 1
    try {
        return URLEncoder.encode(s, "UTF-8");
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
    }
}

public List<String> encodeValuesUsingMethod(String... values) {
    return Arrays.stream(values)
        .map(this::encodeString)        2
        .collect(Collectors.toList());
}
1

Extracted method for exception handling

2

Method reference to the extracted method

This works, and is simple to implement. It also gives you a method that you can test and/or debug separately. The only downside is that you need to extract a method for each operation that may throw an exception. As mentioned in the previous recipe, however, it often allows for easier testing of the component parts of the stream processing.

See Also

Using an extracted method to handle exceptions in lambdas is covered in Recipe 5.9. Using a generic wrapper for exceptions is discussed in Recipe 5.11.

5.11 Using a Generic Exception Wrapper

Problem

You have a lambda expression that throws an exception, but you wish to use a generic wrapper that catches all checked exceptions and rethrows them as unchecked.

Solution

Create special exception classes and add a generic method to accept them and return lambdas without exceptions.

Discussion

Both Recipes 5.9 and 5.10 show how to delegate to a separate method to handle exceptions thrown from lambda expressions. Unfortunately, you need to define a private method for each operation that may throw an exception. This can be made more versatile using a generic wrapper.

For this approach, define a separate functional interface with a method that declares it throws Exception, and use a wrapper method to connect it to your code.

For example, the map method on Stream requires a Function, but the apply method in Function does not declare any checked exceptions. If you want to use a lambda expression in map that may throw a checked exception, start by creating a separate functional interface that declares that it throws Exception, as in Example 5-39.

Example 5-39. A functional interface based on Function that throws Exception
@FunctionalInterface
public interface FunctionWithException<T, R, E extends Exception> {
    R apply(T t) throws E;
}

Now you can add a wrapper method that takes a FunctionWithException and returns a Function by wrapping the apply method in a try/catch block, as shown in Example 5-40.

Example 5-40. A wrapper method to deal with exceptions
private static <T, R, E extends Exception>
    Function<T, R> wrapper(FunctionWithException<T, R, E> fe) {
        return arg -> {
            try {
                return fe.apply(arg);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
}

The wrapper method accepts code that throws any Exception and builds in the necessary try/catch block, while delegating to the apply method. In this case the wrapper method was made static, but that isn’t required. The result is that you can invoke the wrapper with any Function that throws an exception, as in Example 5-41.

Example 5-41. Using a generic static wrapper method
public List<String> encodeValuesWithWrapper(String... values) {
    return Arrays.stream(values)
        .map(wrapper(s -> URLEncoder.encode(s, "UTF-8"))) 1
        .collect(Collectors.toList());
}
1

Using the wrapper method

Now you can write code in your map operation that throws any exception, and the wrapper method will rethrow it as unchecked. The downside to this approach is that a separate generic wrapper, like ConsumerWithException, SupplierWithException, and so on, is needed for each functional interface you plan to use.

It’s complications like this that make it clear why some Java frameworks (like Spring and Hibernate), and even entire languages (like Groovy and Kotlin), catch all checked exceptions and rethrow them as unchecked.

See Also

Lambda expressions with checked exceptions are discussed in Recipe 5.10. Extracting to a method is covered in Recipe 5.9.

1 Why, then, aren’t Java 8 lambdas actually called closures? According to Bruce Eckel, the term “closure” is so heavily overloaded it leads to arguments. “When someone says real closures, it too often means, what closure meant in the first language I encountered with something called closures.” For more information, see his blog post “Are Java 8 Lamdas Closures?”.

2 The Javadocs say the returned values are chosen “pseudorandomly with (approximately) uniform distribution” from that range, which shows the sort of hedging you have to do when discussing random number generators.

3 In the wildly unlikely event that you haven’t already heard this joke: “Rumor has it that this year’s Fibonacci conference is going to be as good as the last two combined.”

4 These examples are taken from the now ancient TV series Get Smart, which ran from 1965 to 1970. Maxwell Smart is essentially a bumbling combination of James Bond and Inspector Clouseau, created by producers Mel Brooks and Buck Henry.

5 You may be wondering why the designers of the Java logging framework didn’t use the same log levels (trace, debug, info, warn, error, and fatal) that every other logging API uses. That’s an excellent question. If you ever find out, please let me know, too.

6 See https://en.wikipedia.org/wiki/Triangular_number for details. Triangle numbers are the number of handshakes needed if each person in a room shakes hands with every other person exactly once.

7 The Unix operating system is based on this idea, with similar advantages.

8 Isn’t that just about the worst-named class in the entire Java API? All exceptions are thrown at runtime; otherwise they’re compiler errors. Shouldn’t that class have been called UncheckedException all along? To emphasize how silly the situation can get, Java 8 also adds a new class called java.io.UncheckedIOException just to avoid some of the issues discussed in this recipe.

9 Interestingly enough, if the values and the divisor are changed to Double instead of Integer, you don’t get an exception at all, even if the divisor is 0.0. Instead you get a result where all the elements are “Infinity.” This, believe it or not, is the correct behavior according to the IEEE 754 specification for handling floating-point values in a binary computer (and caused me massive headaches back when I used to program in—ugh—Fortran; the nightmares have gone away, but it took a while).