Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Deferred Execution With Java's Consumer

DZone's Guide to

Deferred Execution With Java's Consumer

Want to learn the uses of JDK's Consumer interface? This article will define and explain the many uses of the Consumer standard functional interface.

· Java Zone ·
Free Resource

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

In an earlier blog post ("Deferred Execution with Java's Supplier"), I referenced Cay Horstmann's statement in the book "Java SE8 for the Really Impatient," regarding lambda expressions, saying that "the point of all lambdas is deferred execution." Horstmann wrote an article called "Lambda Expressions in Java 8" for Dr. Dobb's magazine in its final year in which he wrote a similar statement using different terminology, "A lambda expression is a block of code that you can pass around so that it can be executed later, just once or multiple times."

In my previous post, I looked at how the standard functional interfaceSupplier is used with lambda expressions in the JDK to support deferred execution for cases. This happens when a single value is "supplied only when necessary" and without any argument passed to it. In this post, I focus on JDK-provided examples of using the Consumer  standard functional interface to "consume" or "process" a particular code block. While the Supplier accepts no arguments and returns exactly one response, the Consumeraccepts one or more arguments and returns no response. The method invoked on a Supplier is the get()method. It is the accept(T) method for a Consumer. By definition, the Consumer is expected to have "side effects" as it "consumes" the provided code block.

There are numerous Consumer-style standard functional interfaces supplied in the java.util.function package. None of these returns is the result (that's why they're consumers!), but they differ in the number and types of arguments they accept. However, they all accept at least one argument. These are listed below:

  • Consumer — The general Consumer accepts a single argument and will be the center of attention for most of this post's examples.
  • BiConsumer — This accepts two arguments instead of one ("two-arity specialization of Consumer")
  • DoubleConsumer — This is a specialized Consumer intended for primitive doubles
  • IntConsumer — This is a specialized consumer for primitive ints
  • LongConsumer — This is a specialized Consumer intended for primitive longs
  • ObjDoubleConsumer — This is a specialized Consumer that accepts two arguments, with the first of type Object and the second of type double
  • ObjIntConsumer — This specialized Consumer accepts two arguments, with the first of type Object and the second of type int
  • ObjLongConsumer — This is a specialized Consumer that accepts two arguments, with the first of type Object and the second of type long

The remainder of this post will look at a subset of the JDK uses of Consumer and related classes to help demonstrate how and when they are useful.

Peeking at the Flow of Stream Elements

In the blog post "Peeking Inside Java Streams with Stream.peek," I discussed the intermediate operation Stream.peek(Consumer) that can be used to view the flowing elements of a stream. This can be very useful for understanding what the various Stream operations are doing to their respective Stream elements. A common way to do this is to have the Consumer provided to the peek method. This will be a call to the System.out.println that prints the currently processed Stream element to standard output (or log the element or print it to standard error). An example of this is provided in the Javadoc documentation for the Stream.peek(Consumer) method:

view plainprint?

Stream.of("one", "two", "three", "four")  
   .filter(e -> e.length() > 3)  
   .peek(e -> System.out.println("Filtered value: " + e))  
   .map(String::toUpperCase)  
   .peek(e -> System.out.println("Mapped value: " + e))  
   .collect(Collectors.toList()); 


Because the various overloaded versions of the println(-) method accept a parameter but do not return anything, they fit perfectly with the "Consumer" concept.

Specifying Action Upon Iterated Stream Elements

While Stream.peek(Consumer) is an intermediate operation, Stream provides two other methods that accept a Consumer and are both terminal operations and "for each" methods. The method Stream.forEach(Consumer) is a method that performs the action specified by the provided Consumer in an "explicitly nondeterministic" manner on the Stream's elements. The method Stream.forEachOrdered(Consumer) performs the action specified by the provided Consumer in "the encounter order" of the Stream if that Stream has an encounter order. In both methods' cases, the Consumer-based "action" should be "non-interfering." Both methods are demonstrated below:

view plainprint?

Set.of("one", "two", "three", "four")  
   .stream()  
   .forEach(i -> out.println(i.toUpperCase()));  

Stream.of("one", "two", "three", "four")  
   .forEach(i -> out.println(i.toUpperCase()));  

List.of("one", "two", "three", "four")  
   .stream()  
   .forEachOrdered(i -> out.println(i.toUpperCase()));  

Stream.of("one", "two", "three", "four")  
   .forEachOrdered(i -> out.println(i.toUpperCase()));

  

The above examples look very similar. The most obvious situation in which forEach could lead to dramatically different results than forEachOrdered is when parallel Stream processing is employed. In that case, it makes the most sense to use forEach instead of forEachOrdered.

Specifying Action Upon Iterable Elements

The previous code examples showed using Stream.forEach(Consumer) methods to iterate a Stream. The examples also demonstrated doing this against a Set and List by first calling stream() on these collections. There are convenience methods, however, that is defined by Iterable and implemented by these collection implementations that accept a Consumer and allow for iteration of that collection using the forEach method. Examples of this are shown in the next code listing:

view plainprint?

Set.of("one", "two", "three", "four")  
   .forEach(i -> out.println(i.toUpperCase()));  
List.of("one", "two", "three", "four")  
   .forEach(i -> out.println(i.toUpperCase()));  


Although I used collections in my example above, anything that implements Iterable will generally support the forEach method (or be in violation of the interface's advertised contract).

Specifying Action Upon Iteration of Map Entries

Although Java's Map interface does not extend the Iterable interface like Set and List do, the Java Mapwas still provided with a similar capability to specify a consumer   to "consume" each entry in the Map. Because a Map has two input arguments (key and value), its forEach method accepts a BiConsumer instead of the Consumer discussed so far in this post. A simple example is shown next.

view plainprint?

Map.of("Denver", "Colorado",  
       "Cheyenne", "Wyoming",  
       "Salt Lake City", "Utah",  
       "Boise", "Idaho")  
   .forEach((c, s) -> out.println(c + " is the capital of " + s));  


Walking the Stack

The StackWalker is an addition to JDK 9 that provides a thread-safe approach to perusing a stack trace and is a significant improvement over the StackTraceElement approach. It's arguably more common for developers to use the StackWalker.walk(Function), but this post is about Consumer and so the focus is on StackWalker.forEach(Consumer). This method is similar to the previously discussed Stream.forEach and Iterable.forEach methods and is demonstrated in the next code listing.

view plainprint?

StackWalker.getInstance().forEach(out::println);  


Although there are many more JDK uses of Consumer, BiConsumer, and other types of standard  Consumer -style functional interfaces, the last examples I'll cover in this post come from the Optional class.

Applying Only When Present

The methods Optional.ifPresent(Consumer) and Optional.ifPresentOrElse(Consumer) defer the execution of the provided Consumers such that the provided Consumer will only be invoked if the Optional is not "empty" (contains a non-null value). This is a simple, but it is also a powerful concept. These simplistic and contrived examples will show you how they work.

view plainprint?

public void demonstrateOptionalIfPresent()  
{  
   getMiddleName(true).ifPresent(n -> out.println("Middle Name: " + n));  
}  

public void demonstrateOptionalIfPresentOrElse()  
{  
   getMiddleName(false).ifPresentOrElse(  
      n -> out.println("Middle Name: " + n),  
      () -> displayMissingMiddleName());  
}  

private Optional<string> getMiddleName(final boolean present)  
{  
   return present ? Optional.of("Wayne") : Optional.empty();  
}  

private void displayMissingMiddleName()  
{  
   out.println("No middle name provided!");  
}  
</string>

  

As the above code listing demonstrates, both Optional.ifPresent and JDK 9-introduced Optional.ifPresentOrElse() only invoke the provided Consumer, if the Optional is not empty. If the Optional is empty, the ifPresent method does nothing and the ifPresentOrElse invokes the second argument (a Runnable).

The standard Java functional interfaces that accept one or more arguments and return no result include the general Consumer, as well as some specialized consumers. These are useful for deferring execution until a given condition occurs (such as being iterated upon or being determined to be present) and the behavior to apply when that condition occurs and involves one or more input arguments with no need to provide a response. The source code examples shown in this post are available on GitHub.

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Topics:
java ,tutorial ,consumer ,stream ,peek ,java streams ,jdk

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}