Tuesday, May 29, 2018

Deferred Execution with Java's Supplier

In the third chapter of his 2014 book "Java SE 8 for the Really Impatient: Programming with Lambdas," Cay Horstmann writes, "The point of all lambdas is deferred execution." He adds, "After all, if you wanted to execute some code right now, you'd do that, without wrapping it inside a lambda." Horstmann then lists several examples of the "many reasons for executing code later" and his last listed reason is "Running the code only when necessary." In this post, I look at some examples of this from the JDK that use Supplier to do exactly that: to execute a "code block" represented as a lambda expression "only when necessary."

When JDK 8 introduced lambda expressions, it also introduced several standard functional interfaces in its java.util.function package. That package's Javadoc documentation states, "Functional interfaces provide target types for lambda expressions and method references." The package contains standard functional interfaces such as Predicate (accepts single argument and represents boolean), Function (accepts single argument and provides single result), Consumer (accepts single argument and does not provide a result), and Supplier (accepts no arguments and represents/supplies single result). [As a side note, the blog post "Java 8 Friday: The Dark Side of Java 8" provides a highly useful "decision tree to [determine] ... the [standard functional interface] you're looking for" when trying to determine which standard functional interface to use (or to roll your own).]

In this post, I'll be focusing on JDK uses of Supplier, implying that the examples covered here are based on JDK-provided methods whose "target types" accept no arguments and supply a single result. These examples will only invoke the single get() method associated with the provided Supplier "when necessary." Note that all JDK uses of Supplier can be found in Supplier's Javadoc-generated HTML representation by clicking on the "Use" link.

Deferring Potential Expensive Operations Until Known to Be Necessary to Log Them

A nice benefit achieved with Supplier-provided deferred execution (sometimes also called "lazy evaluation") is in deferring of an expensive operation for generating a log message until it is known that the result of that expensive operation will actually be logged. In other words, the expensive operation will only be invoked when it is known that it is necessary to do so because the result will be logged. I have blogged about this before in the posts "Java 8 Deferred Invocation with Java Util Logging" and "Better Performing Non-Logging Logger Calls in Log4j2."

Before the modern Java logging libraries and frameworks featured these Supplier-accepting APIs, the common way to avoid incurring expensive operations whose results would not be logged anyway was to use API-based "guard" statements, but these can clutter the code and reduce readability. There are now numerous resources on how to use Supplier-based logging framework APIs to ensure that an expensive operation that might potentially be logged is not actually executed until it is known to be necessary. These resources include my two just-mentioned posts on java.util.logging and Log4j 2 as well as "Writing clean logging code using Java 8 lambdas," "Lazy logging in Java 8," and "Log4j 2 and Lambda Expressions."

The JDK supports this Supplier-powered deferred execution for logging in both its java.lang.SystemLogger "log" methods and in numerous java.util.logging.Logger methods.

Deferring Alternative Calculation for Optional Until Known to Be Necessary

The JDK 8-introduced Optional class provides several methods that accept a Supplier parameter. In all cases, the intent is that the alternative result that the Supplier can provide will only be calculated when it's known to be necessary to calculate it. In other words, the Supplier.get() is only invoked if the Optional is "empty." If Optional is not empty, all of these methods will return the value held in the Optional and won't invoke the Supplier potentially costly operation in those cases. The following list shows the methods on Optional that accept a Supplier as of JDK 10:

Deferring Handling of Undesired null Values Until Known to Be Necessary

JDK 8 added two Supplier-powered methods to the Objects class that allow for an unwanted null (typically on a method parameter) to be detected and an appropriate response supplied only when determined that a null was indeed encountered. I wrote about one of these methods, Objects.requireNonNullElseGet, in my blog post "JDK 9: NotNullOrElse Methods Added to Objects Class." This method will only execute the provided Supplier by calling its get() method when it is determined that that first parameter passed to the method is indeed null. If the first parameter is not null, the Supplier is never executed.

The second Supplier-accepting method added to Objects with JDK 9 is requireNonNull​(T obj, Supplier<String>). This method allows a Supplier to supply a String to be used in a "customized NullPointerException" thrown when the first parameter is null. If the first parameter is not null, the Supplier's get() never needs to be invoked because an exception does not need to be thrown. A very similar method exists on the Objects class that accepts a String instead of a Supplier. This requireNonNull​(T, String) method may be preferable in some cases where the cost of the String generation is deemed less than the cost of generating the supplier. If, for example, the "custom string" is a string literal, it might be preferable to use the form that accepts that String directly instead of the one expecting a Supplier, especially if the null case is expected to be common.

Other JDK Uses of Supplier for Deferred Execution

The JDK examples of using Supplier to defer execution shown in this post were selected because of their relatively frequent use and because they are straightforward examples of how Supplier enables deferred execution or lazy evaluation in cases where no argument needs to be provided and one result needs to be supplied. Other uses of Supplier for deferred execution can be found in the java.util.concurrent concurrency package and in the JDK 8 streams-supporting java.util.streams package. Perhaps the best way to quickly identify the JDK's use of Supplier and its primitive-oriented counterparts is to look at the Javadoc-generated HTML representation of each classes "uses": Uses of Supplier, Uses of DoubleSupplier, Uses of IntSupplier, and Uses of LongSupplier. Interestingly, but not too surprisingly, there are no uses of BooleanSupplier.

The JDK-provided examples of using Supplier discussed in this post mostly demonstrate Horstmann's last listed possible reason for deferring code execution to a later point: "Running the code only when necessary." Other JDK examples of using Supplier demonstrate some of Horstmann's other listed reasons that one might want to "[execute] code later."

Suppliers Are Not Limited to the JDK

Although this post has focused on JDK use of Supplier to defer execution, its use is not limited to the JDK. As discussed earlier, Log4j 2 and other logging frameworks besides java.util.logging also provide Supplier-based logging APIs to allow deferral of construction of expensive strings for logging until it's known that the string will actually be logged. Similarly, one can use Suppliers in one's own code whenever the need arrives to specify code to be executed only when necessary and that code to be potentially exercised does not require an argument passed to it and will return (supply) a single result.

1 comment:

@DustinMarx said...

Simplistic code examples of the concepts discussed in this post are available at https://github.com/dustinmarx/javademos/blob/master/src/dustin/examples/jdk8/SupplierDemo.java.