Monday 12 May 2014

Java 8 is functional, and fun

When I heard that Java was getting 'lambdas' or, as I prefer to think of them, sort-of-closures, as I was an enthusiastic Smalltalk programmer many years ago, I wasn't that excited.  Being Java, I thought that these lambdas would be dull but reliable, which is what I have come to expect of Java.  I had no complaints, because Java is all about solid, reliable, readable code.  I was mistaken - Java lambdas are extremely powerful and huge fun.

What I really love about Java 8 lambdas is the ability to have them implicitly constructed by method references.  That sounds obscure, but it's one of the most powerful and expressive features of Java 8.

I'll show how this works by showing some Smalltalk code.  This code might be from some sort of user interface, and maps key presses to code.

codeMap := Map new.
codeMap at: $A put: #doThis.
codeMap at: $B put: #doThat.

$A and $B are character constants.  #doThis and #doThat are symbols, which in this code hold the name of methods. So, the following code will work:


onKeyPressed: key

keyHandler perform: (codeMap get: key)

The symbolic method name is looked up using the character supplied and the object 'keyHandler' is passed that method name to run.

It's a very concise way of mapping characters to functions, without the need for hard-coded and verbose 'if' or 'switch' statements.

This was impossible in Java.  Until Java 8.  Now, it's very easy.  Here is an example:

public class KeyHandler {

// Map of  Character to a function which has a single parameter - a KeyHandler Instance

public final static Map<Character,Consumer<KeyHandler>> codeMap = new HashMap<>();

// Put method references into the map
static {
   codeMap.put('A',KeyHandler::doThis);
   codeMap.put('B',KeyHandler::doThat);
}


// The map can be used like this
public void respondToKey(char key) {
    Consumer<KeyHandler> function = codeMap.get(key);
    if (function != null) {
        function.accept(this);
    }
}

private void doThis() {}
private void doThat() {}
}

What's going on here is that Java 8 lambda syntax means that the map construction could have been done like this:

codeMap.put('A', (KeyHandler handler) -> {handler.doThis();});
codeMap.put('B', (KeyHandler handler) -> {handler.doThat();});

The type of 'handler' can be inferred because of the type of codeMap, and single statement lambdas don't need brackets:

codeMap.put('A', (handler) -> handler.doThis());
codeMap.put('B', ( handler) -> handler.doThat());

brackets aren't needed for lambdas with one argument:

codeMap.put('A', handler -> handler.doThis());
codeMap.put('B', handler -> handler.doThat());

These lambdas are treated implementations of the interface Consumer<KeyHandler>.  In Java 7 this code could be written using inner anonymous classes:

codeMap.put('A',new Consumer<KeyHandler>() {
   public void accept(KeyHandler handler) {
       handler.doThis();
   }
});

And so on.  There is the slight matter of the 'Consumer' interface not being available in Java 7, but that doesn't matter for this example.

Java 8 lambdas are much more concise, and, it turns out, can be run-time optimised to be typically much more efficient than inner classes, both in terms of memory and speed.

We are nearly there with explaining the use of method references.  In Java 8 a method reference can be used instead of a lambda if the method is, if renamed, the same as the single method in the interface that the lambda is an instance of.   Well, almost.  Assume it is the case for now.  Suppose we had a method that wanted a Runnable as a parameter:

public void doThing(Runnable runnable) { runnable.run(); }

The KeyHandler methods doThis and doThat have no arguments and no return value, and so they are the same as the Runnable method run().  And so, in Java 8, references to these methods can be used as 'Runnable' instances.

KeyHandler handler = new KeyHandler();
doThing(handler::doThis);

Any method that has no arguments and no return value can be used as a Runnable instance in Java 8.

There is one final step to explain what is going on in the KeyHandler class.  Java 8 can have method references even when there is no instance of the class implementing the method.  There is an obvious case of when this is needed:  static methods.  Static methods aren't run by instances.  Here is an example:

public class Thing {
    public static void printSomething() {
        System.out.println("Hello");
    }
}

The method Thing.printSomething() has no arguments and returns nothing, so it can be used as a Runnable:

doThing(Thing::printSomething);

This will print "Hello".

Now let's give Thing an instance method:

public class Thing {
    public static void printSomething() {
        System.out.println("Hello");
    }
    public void printSomethingElse() {
        System.out.println("Goodbye");
    }
}

What does "Thing::printSomethingElse"  mean?  It's an instance method, but no instance is given.  It has no arguments and returns nothing, so it looks like a Runnable.  It isn't.  There is a hidden argument in this method reference.  That hidden argument is the instance that will run the method.

Thing:printSomething else, is, in fact a possible implementation of Consumable<Thing> (or any other interface with a single method that takes a Thing as parameter and returns nothing).

This is how this method reference can be used:

First, we define a new method:

public void doThing(Thing instance,Consumable<Thing> function) {
    function.apply(instance); 
}

Now we use it:

Thing instance = new Thing();
doThing(instance,Thing::printSomethingElse);

This will print "Goodbye".

Finally, we can see what the method references are all about in the KeyHandler class.  They indicate what instance methods are to be used, and also that an actual instance must be supplied.  That's why they are effectively Consumable instances and not Runnable instances even though the methods they refer to have no arguments and return nothing.

So, I finally get to code my Smalltalk idiom in Java!
 

No comments: