Java Reference
In-Depth Information
Consuming Our Temp File Function
In a purely functional world, there is always a return value: a program is simply a function that takes the
string arguments and system environment variables as input and produces an exit code as output. However,
in a postfunctional language such as Java 8, we often want to use side effects instead of return values. This is
why the
Consumer
class is useful. Unfortunately, the API that we have provided so far only accepts functions,
which means that the user always has to specify a return value. Furthermore, when you take a reference to a
method with one argument but returning void, you get an instance of the
Consumer
interface. If we want to
allow users to pass in methods with that shape, we need to accept the
Consumer
interface.
This isn't too tricky, but it does require a bit of finagling. Of course, we want to build off of our existing
APIs instead of duplicating functionality. This means that we will need some bridge from the
Consumer
that
our user passes into the
Function
that our API desires. The difference between
Consumer
and
Function
is the
return value:
Consumer
returns void while
Function
returns a value. For our bridge, we will return a default
value. We could return
null
, but
null
is the evil path to
NullPointerException
. The other default non-value
is
Optional.empty()
, so we can use that. The resulting code looks like Listing 4-4.
Listing 4-4.
Bridge from a Consumer to a Function
/**
* Convert a consumer to a function that consumes the argument and returns
* {@link java.util.Optional#empty()}.
*
* @return A function that consumes the argument and returns the
* empty option.
*/
static <T> Function<T, Optional<A>> functionise(Consumer<T> consumer) {
return a -> {
consumer.accept(a);
return Optional.empty();
};
}
Now that we have that method, we can create our new API. The simple case is one where we require
the user to pass the
IOException
handler. In this case, we have an API just like our previous version in
Listing 4-1, but we take two
Consumer
instances instead of two
Function
instances. The user is responsible
for deciding how to handle the
IOException
, should it occur, as well as how to handle the normal case.
The code for this is in Listing 4-5.
Listing 4-5.
Method Accepting a Consumer to Process a Temporary File, and Calling a Callback on Error
/**
* Creates a temporary file, passes it into the given consumer, and
* deletes the temporary file when the function is complete.
*
* @param consumer The consumer to call with the temporary file, which is
* never {@code null}; may not be {@code null}
* @param exceptionHandler The handler for when an {@code IOException}
* occurs; may not be {@code null}
*/
public static void withTempFile(
Consumer<IOException> exceptionHandler,
Consumer<FunFile> consumer