Java Reference
In-Depth Information
sensibility would be to return null in this case, but null is how you get to the hated NullPointerException .
We can do better. Instead of taking a Consumer , we can take a Function , and so the user specifies the return
value in either case. The resulting code looks like this:
<RETURN_T> RETURN_T withTempFile(
Function<FunFile, RETURN_T> function,
Function<IOException, RETURN_T> exceptionHandler
) {
File file = null;
try {
file = File.createTempFile("funfile", "tmp");
return function.apply(file);
} catch(IOException ioe) {
return exceptionHandler.apply(ioe);
} finally {
if (file != null) file.delete();
}
}
// This is not the Java code you are looking for: read on!
At this point, the users can specify the return value in either case, which means they can do what they
would like with the method. If they realize how evil null is, they can return null . If they want to return
Optional.empty() or some other default value, they can do that. It is maximal flexibility.
There is just one remaining problem with the code above: the order of the arguments is wrong. In
traditional Java code, the order of arguments was largely an implementation detail. When you are writing
a function in a functional style, the order of arguments is very significant. Consider the functional contract
for this method: it is a bifunction that takes two functions as arguments. The first argument is the standard
implementation callback, and the second argument is the error handling callback. Consider how this works as
a lambda, however. If you want to do partial function application, you do it on the first argument, which is the
specification of how to process the file. That implementation is likely to be very specific to the caller's context.
On the other hand, the error handling is more likely to be common throughout an application. Therefore,
it is more useful to have the error handling as the first argument, so that the error handling can be partially
applied, leaving the temp file processing free to be implemented. The resulting method is in Listing 4-1.
Forcing the caller to handle the exception is more intuitive to the classical Java programmer. However,
there are two major issues with this approach. The first is that the calling code is rather ugly, since you have
two functional callbacks on top of each other. It just reads funny to a programmer used to the imperative
style in idiomatic Java. The second and more significant issue is that the functional interface is a bifunction,
which does not fit in many cases. You can't call Stream.map on a bifunction: you need a function for that. To
address both of these issues, we can handle the exception in the return value, which is what we will do in the
next section.
Listing 4-1. Temp File Creation with a Callback for Exceptions
<RETURN_T> RETURN_T withTempFile(
Function<IOException, RETURN_T> exceptionHandler,
Function<FunFile, RETURN_T> function
) {
Objects.requireNonNull(exceptionHandler,
"exception handler for I/O exceptions");
Objects.requireNonNull(function, "function to apply to temp file");
Optional<File> file = Optional.empty();
try {
 
Search WWH ::




Custom Search