Functional Interfaces and Callbacks in Java 8

It’s undeniable that Java 8 was a great step forward for the language. For me, it made Java a bit less rigid than it was, and added some great features to the table. One of those additions was the introduction of Lambda Expressions accompanied by Functional Interfaces. Recently after being away from Java for a while I came back to it for a certain task, and it happened that I found using the aforementioned features to be the most convenient approach. That happened when I needed to make a chain of callbacks. I will leave reading about Lambda Expressions to you, and will just introduce Functional Interfaces.

What are Functional Interfaces?

Simply put, they are interfaces with a single abstract function. If the interface conforms to that single rule, it can be annotated as @FunctionalInterface. The word “abstract” here is important since not all functions in an interface need to be abstract anymore. Java allows programmers to add concrete default functions to an interface. Here are some examples of what does and what doesn’t classify as a Functional Interface.

  • Valid:

interface Example {
    void doSomething();
}
interface Example {
    void doSomething();
    default void doDefault() { ... }
}
interface Example {
    void doSomething();
    default void doDefault() { ... }
    static void doStatic() { ... }
}
  • Invalid:

interface Example {
    void doSomething();
    void doOtherThing();
}

 

What does make Functional Interfaces special?

This question has the same answer as “why are they required to have only one abstract function?”. Since it has only abstract function, one can assign a lambda expression in place of the interface, and that lambda will be considered as the implementation of the abstract function. This will come in handy when we talk next about callbacks.

 

Java callbacks

Remember the bad old days when you had to deal with Event Listeners and pretty much anything which represented a callback. You had to make a class which implements an abstract function of a superclass, and you had to write the full definition of the class, inline or separately. Well, you still have to do that, but now you can just write the definition of your funtction directly, which is way more convenient, and I did it as follows:

Callback interface:

@FunctionalInterface
interface ControllerCallback {
    void processResult(ControllerResult result, Exception ex);
    static ControllerCallback empty() { return new ControllerCallback() {
        @Override
        public void processResult(ControllerResult result, Exception ex){}
        };
    }
}

Here the interface has its lone abstract function processResult() which takes the result of the previous step and an instance of an exception (if any was thrown) as arguments. Additionally, one can add a ControllerCallback to the parameters, but that wasn’t needed in my case. The empty() function returns a callback which does nothing, it was added for testing cases which didn’t need the full pipeline of operations.

Function which uses the callback:

void operation(..., ControllerCallback callback) {
    if (...) {
        callback.processResult(ControllerResult.FAILED_NOT_FOUND, null);
        return;
    }
    
    try {
        ...
    } catch (Exception ex) {
        callback.processResult(ControllerResult.FAILED_OTHER, ex);
        return;
    }
    
    callback.processResult(ControllerResult.SUCCESS, null);
}

The function shows three ways the callback the used: to signal a failure which wasn’t caused by an exception, to signal a failure with an exception, and finally to signal that the operation was done successfully. It’s worth noting that an exception could be instantiated (but not thrown) and passed to the callback to provide extra information about the error.

Usage:

ControllerCallback operationCallback = (res, ex)-> {
    switch (res) {
        case SUCCESS:
            ...
            break;
        default:
            System.err.println("Failed");
            if (ex == null)
                System.err.println("No exception was thrown.");
            else
                System.err.println("An exception was thrown:\n"
                                   + ex.getMessage());
    }
};
controller.operation(..., operationCallback);

Notice how the callback was instantiated, it was assigned only a single Lambda representing the implementation of the function. This is much better than the old tedious way that Java forced us to use for a long time.