Tuesday, November 30, 2021

Quiz yourself: Functional interfaces and error handling in Java

Oracle Java, Oracle Java Exam Prep, Oracle Java Tutorial and Materials, Oracle Java Career, Oracle Java Jobs, Oracle Java Certification

Simon and Mikalai raise the question of how functional-style software should represent a recoverable error.

Download a PDF of this article

Imagine that you are developing a high-load message board application that will use nonblocking communication with a database. This communication will be handled using the following service class:

public class MsgBoardService {

  public int getCommentCount(int threadId) throws SQLException {

    ... // implementation logic here

  }

  ...   // more similar methods 

}

Which functional interface could be used to expose the MsgBoardService getCommentCount method? Choose one.

A. Function

B. Supplier

C. IntUnaryOperator

D. Callable

E. A custom interface

Answer. You have a method that takes a single argument of int type and returns an int. Let’s walk through each of the proposed functional interfaces to see how well they match up with these types.

The Function<A,R> interface declares a method that returns an object of type R and takes a single argument of type A. Of course, these generic arguments must be of reference type, but primitive values can be handled using autoboxing if you are willing to sacrifice a little efficiency.

The Supplier<R> interface declares a method that returns an object of type R but takes no arguments. That’s not a great fit for tasks. It is possible to write a function factory that would embed the necessary argument into a closure and then have that new function delegate to the getCommentCount method. This, however, is clearly a rather circuitous route and can’t really be considered a proper fit.

The IntUnaryOperator interface declares a method that returns a primitive int and accepts a primitive int as its argument. This looks promising as IntUnaryOperator is a perfect fit in terms of the argument and return types, but let’s not jump to conclusions.

The Callable<R> interface declares a method that takes no arguments and returns an object of type R. At this level of investigation, this appears to be the same situation as with Supplier—but why would two identical interfaces exist? Well, of course, they’re not identical. The Callable call() method is declared with throws Exception, whereas the Supplier, in keeping with all the interfaces of the java.util.function package, does not declare any throws clause.

This distinction highlights the observation that the getCommentCount method is declared as throws SQLException, which is a checked exception and, thus, regardless of the argument and return types, the Function, Supplier, and IntUnaryOperator interfaces are all unsuited to this question’s task because they cannot support the checked exception. Since Callable is also unsuited because it cannot take an argument, you must conclude that options A, B, C, and D are incorrect and that a custom interface is necessary. Therefore, option E is correct.

The custom interface could look like this.

public interface ExceptionIntUnaryOperator {

  int apply(int i) throws Exception;

}

There are some side notes worth discussing that arise from this question. Perhaps the most compelling is, “Since so many of Java’s core libraries report problems with checked exceptions, why do the standard functional interfaces in java.util.function not declare any checked exceptions?” There are two levels of answer.

◉ To be specific about a case, if an exception is thrown by a method that’s passed to a map method of a stream, the entire stream processing method will crash. That’s not a failure of the stream implementation. If you think about it, there’s no clean way to embed exception handling (which by nature must be specific to the application domain) into the general stream code.

◉ At a more fundamental level, in functional programming a pure function always returns a value of the expected type. If it doesn’t always return such a value, it’s called a partial function.

The only use for exceptions is to represent catastrophic errors either in the design or the implementation of the code. Such errors should result in a system shutting down, not in a recovery attempt. This is simply because if the system has done something that by the intended design should be impossible, it’s impossible to know how messed up everything might be, and you have no solid ground from which to attempt a recovery.

This observation then raises the question of how functional-style software should represent a recoverable error (such as a database being offline, which merely requires the plug to be reconnected to permit a retry). Generally, the returned value of a function that might fail will be an object (or tuple) that contains one of two values: It must either return the successful result or provide the reason for failure. Such a class is often called an Either; such a class exists in the Vavr functional library, for example.

If you don’t care about the reason for a failure, and the only reason for the absence of a result is failure, the Optional class in the core Java APIs could be used instead. In that case, you could put this system together using IntFunction<R>, which declares a method that takes a primitive int and returns an object of type R. The result could look like this.

IntFunction<OptionalInt> msgCommentCounter = threadId -> {

  try {

    return OptionalInt.of(getCommentCount(threadId));

  } catch(SQLException sqle) {

    return OptionalInt.empty();

  }

};

Conclusion. The correct answer is option E.

Source: oracle.com

Related Posts

0 comments:

Post a Comment