Deferred Execution With Java's Supplier
Supplier is a versatile tool for development, so let's look at how you can use its offerings for lazy evaluation, and how to tell when that's the right choice.
Join the DZone community and get the full member experience.
Join For FreeIn 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 a single argument and represents a boolean
), Function (accepts a single argument and provides a single result), Consumer (accepts a single argument and does not provide a result), and Supplier (accepts no arguments and represents/supplies a 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:
- Optional.or(Supplier): Executes the
Supplier.get()
only if theOptional
is empty and then theSupplier
provides anOptional
; otherwise returns essentially the non-emptyOptional
as-is (Optional
with value present). - Optional.orElseGet(Supplier): Executes the
Supplier.get()
only if theOptional
is empty; otherwise returns the value present in theOptional
. A similar method also exists on the primitive-specific versions ofOptional
: OptionalDouble.orElseGet(DoubleSupplier), OptionalInt.orElseGet(IntSupplier), and OptionalLong.orElseGet(LongSupplier). - Optional.orElseThrow(Supplier): Executes the
Supplier.get()
only if theOptional
is empty and then theSupplier
provides aThrowable
to be thrown; otherwise returns the value present in theOptional
. A similar method also exists on the primitive-specific versions ofOptional
: OptionalDouble.orElseThrow(Supplier), OptionalInt.orElseThrow(Supplier), and OptionalLong.orElseThrow(Supplier).
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 class's "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 Supplier
s 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.
Published at DZone with permission of Dustin Marx, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments