Understanding Java's functional interfaces

Introduction

Java logo "Functional interfaces" are interfaces whose implementation can be provided by a lambda expression. That is, if a method has such an interface as a parameter then, rather than defining a class that implements the interface, we can just specify an operation in the method call body. When used carefully, this technique allows for compact, expressive code.

If that all sounds rather abstract, don't worry -- in this article I will provide a specific, simple example that demonstrates where such a technique could be useful.

Functional interfaces have been available since Java 8 but, in my experience, they aren't widely used outside the standard runtime library. To be fair, the syntax can look a little peculiar at first. Many examples to be found in the standard runtime library are not easy to comprehend, because they combine functional interfaces with other rather abstract techniques, such as parameterized classes. While parameterized classes are important in their own right, this article does not require any understanding of parameterized classes. I also don't assume any knowledge of lambda functions -- something I can really only get away with because my examples are so simple. To use functional interfaces effectively, you really do have to understand how to use lambda expressions.

Stating the problem

Consider a class Message that represents some kind of message in a communication system. The Message class has a sign() method, that stores or applies some sort of digital signature to the message. The Message class has many complex methods, but all we're concerned with here are the methods sign(), and getText(). This latter method simply returns a textual representation of the message body. So, in outline, the Message class looks like this.

class Message
  {
  // Many other methods...
  // Sign a message using the specified signer
  public void sign (Signer t)
    {
    String sig = t.getSignature (this);
    // Store the signature ...
    }

  // Return the message body
  public String getText () { return ...; };
  }

The sign() method does not actually generate a digital signature -- it just delegates that to something called Signer. This is an interface that defines one method, getSignature():

iinterface Signer
  {
  public String getSignature (Message m);
  };

The message-signing logic might look like this:

 Message m = new Message ("Hello");
 m.sign (/* ??? */);

The question, then, is: how to provide the argument to m.sign()? We anticipate that the application might have several implementations of the Signer interface, so that different methods of message signing can be provided. The problem is to provide implementations that are both compact, and expressive.

Pre-Java 8 implementations

The most natural, comprehensible implementation, which will work with any Java going back to JDK1.1, is to define specific classes that implement the Signer interface. Consider a very trivial implementation called HashSigner that generates a signature from a hash of the message body. Here's how HashSigner might be implemented.

class HashSigner implements Signer
  {
  @Override
  public String getSignature (Message m)
    {
    // Crude implementation
    return "" + m.getText().hashCode();
    }
  }

Since HashSigner implements the Signer interface, the sign() function in Message can call the getSignature() method via the interface. The Message class need have no knowledge of how HashSigher works, or even that it exists -- that's the power of interfaces.

A problem with this implementation is the amount of boilerplate code needed for what is, in the end, a trivially simple operation. It's easy to understand but, in many cases, the actual logic becomes swamped with scaffolding code that plays no real part in the implementation.

There is an alternative formulation using anonymous inner classes that has also been available for a very long time. This method of implementing an interface without a named class is hugely popular, and almost any substantial Java program or library will use it extensively. Here's how we can perform the same, trivial message-signing logic without defining a new class:

    m.sign (new Signer ()
      {
      public String getSignature (Message m)
        {
        return "" + m.getText().hashCode();
        }
      });

Notice that the implementation logic here is exactly the same as the previous example. Moreover, this implementation does define a class that implements the Signer interface, but it is anonymous. The compiler will generate a .class file with a machine-generated name.

The problem with this formulation is that it uses only a little less boilerplate than the conventional one, and the syntax is not particularly elegant. Nevertheless, it's rare to look at any substantial Java project, prior to Java 8, that does not have hundreds of examples of this kind of coding.

Java 8 and later -- using lambda functions

Java 8 and later provide a much more compact representation, using functional interfaces and lambda functions. This isn't really the place to provide a detailed description of a lambda function but, essentially, a lambda function (also known as a closure or an anonymous function) is a specification of code to be executed on particular arguments. Traditionally, of course, "code to be executed on particular arguments" would amount precisely to a function. A lambda function, however, is like a function implementation without the function definition.

Consider this very simple function:

double sqr (double x) { return x * x; }

This function takes a double argument, and returns the square of its argument by multiplying it by itself. So far, so ordinary. A lambda expression that defines the same behaviour might be:

(a)->(a*a)

This expression doesn't have a name, so it's not a full function. We can't call it -- not in this form, anyway. It takes one argument a, and we need not define the type of the argument if the compiler can work it out from the implementation. Here's how the message signing function can be implemented using a lambda expression:

  m.sign (a -> "" + a.getText().hashCode());

You'll see that this formulation has no boilerplate -- just the logic. The logic itself -- calling hashCode() on the message body is exactly the same as in the previous two examples. Here, though, there is no scaffolding -- not even an anonymous class.

But how does this formulation actually work? The method Message.sign() expects a class that implements the Signer interface. Where's the class? How does the JVM know that the supplied lambda expression is, in some way, an implementer of the Signer interface?

The interface Signer is a functional interface. That is, it is an interface with a structure that makes it applicable to use in lambda formulations. Notice that the interface only defines one method so it can, in theory, be invoked without specifying the method name -- there's no chance of ambiguity. This single method -- getSignature() -- takes one argument of type Message. So a lambda expression can legitimately be used in place of an implementation of Signer, so long as the lambda expression takes one argument that can be interpreted as taking a Message argument.

In our example, The parameter of the lambda expression is simply a, defined without any type. However, the operation to be applied to it -- a.getText() is a method call on Message. So, in short, the lambda expression

  a -> "" + a.getText().hashCode())

can be used passed as an argument to any function that takes as a parameter any functional interface that specifies a method that takes a Message argument.

There's no doubt that this is a compact, elegant way to call a function with a specified operation as its argument. It's not necessarily a technique to be used with abandon, as I'll explain later; but it's much more concise than the use of anonymous inner classes -- at least where the specified operation is relatively easy to express.

What makes a functional interface?

Java 8 and later define a built-in annotation @FunctionalInterface that can be used to mark an interface as being functional. However, it's not the annotation -- which is optional -- that is significant, but the structure. A functional interface defines exactly one abstract method. Actually that's not exactly true, but the subtleties need not worry us here. This definition of Signer would fail:

@FunctionalInterface
interface Signer
  {
  public String getSignature (Message m);
  public String foo (int f);
  };
With the @FunctionalInterface annotation in place, the definition would fail, whether or not Signer was actually used elsewhere in the code. The error message would be of this form:
error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
  Signer is not a functional interface
    multiple non-overriding abstract methods found in interface Signer

Without the annotation, the compiler would eventually raise an error if the code tried to use Signer as a functional interface -- that is, if it tried to use a lambda expression to stand in for the interface.

For the sake of completeness, I should point out that to be a functional interface only places restrictions on abstract methods, that is, methods that require to be implemented in some class. There are no restrictions on static or default methods.

Closing remarks

Functional interfaces open the way to using a whole new programming paradigm with Java. They allow for compact code which has some expressive power. However, functional interfaces are potentially a cause of confusion. The main problem is that there's no way to look at a piece of code like

  m.sign (a -> "" + a.getText().hashCode());

and to see immediately what use will be made of the lambda expression. Compare this with the example that used an anonymous inner class: with the anonymous inner class it was perfectly clear that we were invoking a method called getSignature() on a thing called Signer. This is less obvious when the interface itself is non-obvious in the code.

Perhaps clarity would be improved if I wrote

  m.sign (Message a -> "" + a.getText().hashCode());

or

  m.sign (a_message -> "" + a_message.getText().hashCode());

In any case, the use of functional interfaces creates opportunities to think hard about the expressive power of names, and where additional comments might be appropriate. Carelessness in this area is not really a problem when you're writing code -- it's just regrettable when you come to fix a bug in it five years later.