Lambda Expressions

Definition

Implementation of the Single Abstract Method (SAM) of a Functional Interface.

BinaryOperator<Long> longAdder = (Long x, Long y) -> {return x + y;};

This line of code does not add two long values. longAdder is the implemention of the apply method in BinaryOperator interface. When the apply method is called on longAdder, two long values will be summed.

longAdder.apply(Long.valueOf(1), Long.valueOf(2)); // Long[3]

Syntax

(parameters) -> {lambda-body} // -> is the lambda operator

Syntax for Parameters

@FunctionalInterface
interface Foo {
    void foo();
}

@FunctionalInterface
interface Baz {
    void baz(Integer i, String s);
}

class App {
    void fooBarBaz() {
        Foo foo = () -> {};
        Baz baz = (Integer i, String s) -> {};
    }
}
@FunctionalInterface
interface Baz {
    void baz(Integer i, String s);
}

class App {
    void fooBarBaz() {
        Baz baz = (i, s) -> {};
        // This is not allowed:
        // Baz baz = (Integer i, s) -> {};
    }
}
@FunctionalInterface
interface Baz {
    void baz(Integer i, String s);
}

class App {
    void fooBarBaz() {
        Baz baz = (final Integer i, final String s) -> {};
    }
}
@FunctionalInterface
interface FooTest {
    void bar(Integer i);
}
class App {
    void fooBarBaz() {
        FooTest bar = i -> {};
        // This is not allowed:
        // FooTest bar = Integer i -> {}; 
    }
}

Syntax for Lambda-Body

@FunctionalInterface
interface Foo {
    void foo(String s);
}

@FunctionalInterface
interface FooTest {
    Integer bar(String s);
}

class App {
    void fooBar() {
        Foo foo = s -> {System.out.println(s);};
        FooTest bar = s -> {return -1;};
    }
}
@FunctionalInterface
interface Foo {
    void foo(String s);
}

class App {
    void fooBar() {
        Foo foo = s -> {};
    }
}
@FunctionalInterface
interface Foo {
    void foo(String s);
}

@FunctionalInterface
interface Bar {
    int bar(String s);
}

class App {
    void fooBar() {
        Foo foo = s -> System.out.println(s);
        Bar bar = s -> -1; // return -1;
    }
}
@FunctionalInterface
interface Foo {
    void foo(String s);
}

@FunctionalInterface
interface Bar {
    Integer bar(String s);
}

class App {
    void fooBar() {
        Foo foo = s -> new StringBuilder().append(s).append(s).append(s);
        Bar bar = s -> null; // return null;
    }
}
@FunctionalInterface
interface Foo {
    Integer foo(int i);
}

class App {
    void foo() {
        Foo foo = i -> Math.abs(i); // return Math.abs(i);
    }
}

Target Typing and Type Inference

Runnable r  = () -> {}; // Runnable here is the Target Type

Lambda Expression Contexts

new Thread(() -> {});
Runnable r = () -> {};
Runnable runnable() {
    return () -> {};
}
Runnable r = aBoolean ? () -> {} : () -> {};
Supplier<Runnable> runnableSupplier = () -> () -> {};
Object o = (Runnable) () -> {};
Runnable[] arr = new Runnable[]{() -> {}, () -> {}};

Scoping Rules

@FunctionalInterface
interface Foo {
    int FOO_CONST = -1; 
    int foo();
}

class FooTest {
    void fooTest() {
        // Not allowed: Foo foo = () -> {return FOO_CONST;};        
        Foo foo = () -> Foo.FOO_CONST;
    }
}
@FunctionalInterface
interface Foo {
    int foo(int i);
}

class FooTest {
    void fooTest() {
        int i = -1;
        // Not allowed: Foo foo = (i) -> -1;
        // Not allowed: Foo foo = (k) -> {int i = -5; return k;};
        // This is fine:
        Foo foo = (k) -> {k = 10; return k;};
    }
}
@FunctionalInterface
interface Foo {
    int foo();
}

class FooTest {

    int i = -1;

    void fooTest() {
        // Shadows the instance field i, returns 10 not -1:
        Foo foo = () -> {
            int i = 10;
            return i;
        };
    }
}
@FunctionalInterface
interface Foo {
    int foo();
}

class FooTest {
    int i = -1;
    void fooTest() {
        Foo foo = () -> {
            int i = 1;
            return this.i;  // returns -1, not 1!
        };
    }
}
import java.util.function.IntSupplier;

class Foo {
    private int foo() {
        return 42;
    }
    public int bar() {
        // Compiles fine
        return ((IntSupplier) () -> foo()).getAsInt();
    }
}

Value Capture

// aLong here is effectively final, this code compiles fine
long aLong = 1L;
LongUnaryOperator op = l -> aLong;

long anotherLong = 1L;
anotherLong = 2L; // not effectively final!
// This code will not compile
// LongUnaryOperator op = l -> anotherLong;
void foo() {
    int i = 10;
    // Will not compile since i is modified
    // IntSupplier is = () -> i++;    
}
class Foo {
    int i;
    void foo() {
        // fine since i is an instance variable
        IntSupplier is = () -> i++;
    }
}
class Foo {
    final int i = -1;
    void foo() {
        // will not compile
        // IntSupplier is = () -> i++;
    }
}

Void Compatibility Rule

If the body of the lambda is a statement expression, the lambda can still be assigned to a Functional Interface that has a void return type, even if the statement expression has a return type.

// Single Abstract Method in the Consumer interface is `void accept(T t)` 
// Boolean.valueOf(true) actually returns a Boolean object
// However the following compiles fine according to the above rule mentioned
Consumer<Integer> ic = i -> Boolean.valueOf(true); 

// This does not compile:
// Consumer<Integer> ic = i -> {return Boolean.valueOf(true);};

// This does not compile either:
// Consumer<Integer> ic = i -> true;

// This does not compile either, you get the idea..
// Consumer<Integer> ic = i -> 1 == 1;

Recursion with Lambda Expressions

import java.util.function.IntUnaryOperator;

class Factorial {

    IntUnaryOperator fact;

    Factorial() {
        fact = i -> {
            if (i == 0) {
                return 1;
            } else {
                return i * fact.applyAsInt(i - 1);
            }
        };
    }

    public static void main(String[] args) {
        final Factorial factorial = new Factorial();
        System.out.println(factorial.fact.applyAsInt(5)); // 120
    }
}

References


🏠