Understanding Lambda/Closure, Part 7 - JDK8 Lambda Syntax



This article begins where Understanding Lambda/Closure, Part 6 left off. We're finally moving into the introduction of JDK8 Lambda syntax. Basically, expressing (x -> x * 2) in JDK8 can be as follow:

(Integer x) -> x + 2

Don't forget that Java is a statically-typed language, so the type declaration is necessary here. In Understanding Lambda/Closure, Part 4, we said that type inference is important for statically-typed languages when using lambda/closure. JDK8, of course, definitively provides stronger type inference, so you can omit type declaration in some contexts. Don't be too eager; we're just getting started. Even so, the syntax above is much cleaner than the anonymous class we've seen in Understanding Lambda/Closure, Part 6.

With JDK8 Lambda, writing a compose method to do function composition f(g(x)) can be as follows:

public static <A, B, C> Func<A, C> compose(Func<A, B> f, Func<B, C> g) {
    return x -> g.apply(f.apply(x));
}

We still need the interface Func defined in Understanding Lambda/Closure, Part 6. The method body, however, is much cleaner. If you ignore the typing ".apply", x -> g.apply(f.apply(x)) will be x -> g(f(x)), which clearly expresses the intent we want to do. Using the compose method to do function composition g(f(x)), where f(x) = x + 2 and g(y) = y * 3, can be as follow:

compose((Integer x) -> x + 2, (Integer y) -> y * 3);

I list the code using anonymous classes in Understanding Lambda/Closure, Part 6 again in the following. Compare these two codes. Which one will you choose to write?

compose(
    new Func<Integer, Integer>() {
        public Integer apply(Integer x) {
            return x + 2;
        }
    },
    new Func<Integer, Integer>() {
        public Integer apply(Integer y) {
            return y * 3;
        }
    }
);

The general syntax of JDK8 Lambda consists of a parameter list, the arrow token ->, and a body. Here are two examples of lambda expressions:

// Map two integer x and y to x + y.
(int x, int y) -> x + y

// Take no argument; just returns an integer 42.
() -> 42

In JDK8 Lambda, the body can either be a single expression or a statement block. For example:

// Take a string and prints it to the console; return nothing.
(String s) -> { out.println(s); }

// Take one integer and return an integer.
(Integer x) -> {
    Integer result;
    ...other statements
    ...
    return result;
};

A block with multiple statements is allowed but not recommended. Always using simple expressions is a good idea when using lambda. If you really have a complex implementation, there are still other ways to adopt the advantages of JDK8 Lambda. I will talk about them in later articles.

In several languages, a lambda expression itself has a type, such as an anonymous function is an instance of Function in JavaScript. In JDK8, a lambda expression (or statement) itself is neutral. Without a target type, a lambda expression doesn't represent any kind of object. How to define the target type of a lambda expression? Java was not born to be a language with first-class functions. We've seen in Understanding Lambda/Closure, Part 5 that, avoiding a new complex type system and keeping compliance with the existing APIs are the two chief considerations when adopting lambda in Java. JDK8 doesn't introduce new function types; it defines what functional interfaces are for types that lambda expressions represent. A functional interface is an interface with just one abstract method. Many existing interfaces have this property, such as Runnable, Callable, Comparator, etc.

public interface Runnable {
    void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

public interface Comparator<T> {
    int compare(T o1, T o2);
}

The Func interface defined in Understanding Lambda/Closure, Part 6 is also a functional interface. The target type of a lambda expression is inferred from the corresponding functional interface. For example, the following lambda expression is an instance of Func.

Func<Integer, Integer> func = x -> x * 2;

The type inference works here. The types of the parameter x and the return value are inferred from the generics declaration and the method signature, so you don't have to declare types in the lambda expression. If there's a functional interface defined as follow:

public interface Function<P, R> {
    R call(P p);
}

In the following example, the same expression (x -> x * 2) will be an instance of Function; the types of the parameter and the return value will be Double.

Function<Double, Double> f2 = x -> x * 2;

So, a lambda expression itself is neutral. It doesn't care about the name of a functional interface. It only cares about the method signature, but ignores the method name.

A functional interface is an interface with only one abstract method, but it's sometimes hard to figure out directly that an interface is a functional interface. For example, an interface may contain default methods (a new feature in JDK8), extend other interface, override some methods, and etc. All these will make it hard to check a functional interface. There’s a new annotation introduced - @FunctionalInterface. It can be used as follow:

@FunctionalInterface
public interface Func<P, R> {
    R apply(P p);
}

If an interface annotated by @FunctinalInterface is not a functional interface, there would be a compiler level error. For example:

@FunctionalInterface
public interface Function<P, R> {
    R call(P p);
    R call(P p1, P p2);
}

This will produces a compiler error such as:

@FunctionalInterface
^
  Function is not a functional interface
    multiple non-overriding abstract methods found in interface Function

It seems that the lambda syntax is just compiler sugars of anonymous classes. Really? Let's take a look at the following program. What will it print?
import static java.lang.System.out;

public class Hello {
    Runnable r1 = new Runnable() {
        public void run() {
            out.println(this);
        }
    };
    Runnable r2 = new Runnable() {
        public void run() {
            out.println(toString());
        }
    };

    public String toString() { return "Hello, world!"; }

    public static void main(String[] args) {
        new Hello().r1.run();
        new Hello().r2.run();
    }
} 

It will print something like Hello\$1@103368e and Hello\$2@1f2ae62. That's because the meaning of this and the method name toString comes from the respective instance of anonymous classes. Let's take a look at the following program, too. What will it print?
import static java.lang.System.out;

public class Hello {
    Runnable r1 = () -> { out.println(this); };
    Runnable r2 = () -> { out.println(toString()); };

    public String toString() { return "Hello, world!"; }

    public static void main(String[] args) {
        new Hello().r1.run();
        new Hello().r2.run();
    }
}

It will prints "Hello, world!" twice. That is, the names this and toString in the body of a lambda expression are interpreted from the enclosing environment - the Hello instance. Also note that, the compose method defined previously doesn't need the final keyword in the parameter list. In Understanding Lambda/Closure, Part 5, we've seen that, Java compiler always forces you to add a modifier final in front of the local variable, even if the variable isn't assigned again in the anonymous class. JDK8 relaxes this restriction. If a variable is an effectively final local variable, that is, if a variable isn't assigned again in a lambda expression, we can omit the final keyword.

But can we change the value of the captured variable in the lambda expression, such as what we can do for free variables in JavaScript or Scala? Because writable free variables also mean mutable states, and they'll cause locking problems in concurrent programming – one of the reasons that JDK8 adopts lambda, prohibiting capture of mutable local variables is the intent of JDK8. You cannot change the value of the captured variable in the lambda expression

You've seen basic syntax of JDK8 Lambda. As we've seen that, keeping compliance with the existing APIs is one of the chief considerations when adopting lambda in Java. What strategies will Java - an old language with bunch of existing APIs - take to solve this question? That's what we'll look at in the next article.