Lambda Expressions

functional interface is an interface that defines one and only one abstract method.

As we saw in the previous lessons, anonymous classes are used in expressions that define and instantiate unnamed classes.  When the anonymous class implements a functional interface, the anonymous class needs to define only one method.  Since the method’s name is defined in the functional interface, there is really no need to specify the name of the method in the anonymous class.

This pattern is so common that Java provides through lambda expressions a simple way of declaring anonymous classes that implement a single method.

A lambda expression has two parts.  Those two parts are separated by a lambda operator, ->.  The expression on the left side of the -> specifies within parenthesis the parameters of the single abstract method.  The expression on the right side of the -> specifies the body of the method.  When the body of the lambda expression consists of a single statement, the value of the statement is returned.

For example below we see an interface that defines a single method that returns an integer.

interface Foo {
    int bar();
}

To create an instance of Foo we can use a lambda expression as shown below.

Foo f = () -> 10;         // () -> 10 defines an implementation of Foo
int i = f.bar();          // call bar
System.out.println(i);

When creating a single statement lambda expression, the expression implicitly returns the value on the right hand side of the lambda operator.  In the example above, the value 10 is returned by the method named bar.

As you can see below, doing the same thing with anonymous classes is more cumbersome.

Foo f = new Foo() {
    public int bar() {
        return 10;
    }
};
int i = f.bar();
System.out.println(i);

A functional interface can also declare a method that has one or more parameters.  Since the types of the parameters are defined in the interface, lambda expressions can omit them and infer them.

interface Bar {
    boolean isEven(int n);
}
...
Bar b = (n) -> (n % 2) == 0
System.out.println(b.isEven(3));

Lambda expressions make it easy to redefine methods.

interface Moe {
    boolean gt(int a, int b);
}
...
Moe m = (i,j) -> i > j;            // define gt
System.out.println(m.gt(2,2));     // prints False

m = (i,j) -> i >= j;               // redefine gt
System.out.println(m.gt(2,2));     // prints True

Functional interfaces can also be generic as the next example shows.

interface Daa<T extends Comparable> {
    T max(T x, T y);
}
...

Daa<Integer> d = (x, y) -> (x.compareTo(y) > 0) ? x : y;
System.out.println(d.max(10, 20));

Block Lambda Expressions

We can also specify a block of code on the right hand side of the lambda operator rather than a single expression, like in the example below.  In that case we refer to the lambda expression as a block lambda.

interface Cee {
    int max(int[] arr);
}
...

Cee c = (arr) -> {
    if (arr.length == 0)
        return -1;

    int max = arr[0];
    for (int i : arr)
        max = (i > max) ? i : max;

    return max;
};

int[] arr = {1,2,3};
int m = c.max(arr);
System.out.println(m);

Variable Capture

Lambda expressions can use the instance and static variables defined by their enclosing classes.  They also have access to the instance of the enclosing class with the use of this.  When a lambda expression is declared inside a method, the lambda expression can get, but not set, the value of local variables declared within the method so long as the variables are effectively final.  An effectively final variable is a variable that is not necessarily declared as final, but which is not modified anywhere within its scope.

interface Joi {
    void foi();
}
...

void method() {
    var i = 1;          // local variable i
    Joi j = () -> {
        int k = 1 + i;  // ok to get the value of i
        i++;            // illegal - not allowed to modify i
    }
    i++;                //illegal if i used inside lambda expr
}

© 2017 – 2019, Eric. All rights reserved.