Functional Java I - Single Method Interfaces

Java introduced functional programming features some time ago. In fact, it is nearly 7.5 years ago that Java 8 was released with it. And it took me only a bit over a year to write about it in my blog post called Functional Java.

The other day, I noticed that a colleague was not aware of some not so obvious way Java treats lambdas (for an explanation of lambdas, see What are lambdas). That inspired me to write this blog post.

TL;DR: Java lets you use lambdas as ad-hoc implementations of single method interfaces. This has some interesting consequences I would like to explore in some more detail here.

Lambda

A single method interface is an interface that, surprise, only has a single abstract method. These are also called functional interfaces.

Java has a lot of single method interfaces, for example Runnable.

Now instead of this:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello Anonymous Class World");
    }
});

We can write this:

Thread thread = new Thread(() -> System.out.println("Hello Lambda World"));

Here we see that the lambda is an instance of Runnable.

Method References

Instead of creating an anonymous function with lambda, you can also use the double colon operator to get a method reference. For example, to print the contents of a list with one entry per line you can do this:

Arrays.asList("ab", "c", "def").forEach(System.out::println);

Using Method References for Compatibility

In golang, you can implement an interface by just ensuring that a type has implementations for all methods in the interface. It is not necessary to reference the interface explicitly. In Java, you must explicitly implement an interface.

Suppose you have the following:

interface SomeInterface {
    int op(int a, int b);
}

public class SomeInterfaceImpl {
    public int op(int a, int b) {
        return a + 2*b;
    }
}

interface OtherInterface {
    int op(int a, int b);
}

public static int otherInterfaceConsumer(
        List<Integer> list, int a,
        OtherInterface otherInterface) {
    return list.stream().map(x -> otherInterface.op(a, x))
            .reduce(0, Integer::sum);
}

Something like this might happen because you have a library that provides an implementation and another one that provides a consumer and both have an interface that is semantically equivalent. You cannot just provide an instance of SomeInterface to otherInterfaceConsumer because the type does not match.

otherInterfaceConsumer(Arrays.asList(1, 4, 6), 2, new SomeInterfaceImpl());
// incompatible types: SomeInterfaceImpl cannot be converted to OtherInterface

However, with the double colon operator, you can let the Java compiler do the wiring for you:

otherInterfaceConsumer(Arrays.asList(1, 4, 6), 2, new SomeInterfaceImpl()::op);