Lambda Expressions

Author: Tatyana Milkina

Lambda expressions are introduced in Java 8 to facilitate functional programming and simplify the development.

A lambda expression can be called an anonymous function or a method without declaration, which can be created without belonging to any class.

1. Syntax

Syntax of a lambda expression in java:

(arguments) -> (body)

Examples of lambda expressions:

(int a1, int a2) -> {  return a1 - a2; }
() -> System.out.println("Lambda Expression");
(String s) -> { System.out.println(s); }
() -> { return 67 };
() -> 89
  • Lambda-expressions can have zero or more input parameters:
  • () -> 89 //zero parameters
    (String s) -> { System.out.println(s); } //one parameter
    (int a1, int a2) -> {  return a1 - a2; } //multiple parameters
    
  • The type of parameter can be declared explicitly or be inferred by the compiler from the value of the parameter. For example:
  • (String s) -> { System.out.println(s); }
    
    can be written as:
    (s) -> { System.out.println(s); }
    
  • If there are no parameters or multiple parameters, parentheses are required. Multiple parameters are separated with commas:
  • (a1, a2)
    (int a1, int a2)
    () -> 42
  • No need to declare a single parameter in parentheses. But in this case, you cannot provide the type explicitly. You should leave it to the compiler to infer the type of that single parameter.
  • a1 -> return 2 * a1
  • Lambda-expression body can contain zero or more expressions.
  • No need to use curly braces and the 'return' keyword, if a body consists of a single expression or a statement block.
  • If a body consists of multiple expressions, curly braces and 'return' keyword are required. If the 'return' keyword is skipped, the return value is void.
  • When the type of parameters is specified, you need to specify all or none.
  • Lambda expressions can access instance and static variables.
  • Lambda expressions can access local variables from the enclosing scope. The variable needs to be final or effectively final. This behavior of lambdas is similar to accessing variables in outer scope from local and anonymous classes.
  • If a lambda expression throws a checked exception, then the method in the functional interface should declare that or it will result in a compiler error.
  • Default methods of a functional interface cannot be accessed from within lambda expressions.
  • The lambda expression cannot use a parameter name similar to a variable name of the enclosing context. For example, this code won't compile:
  • String str = "";
    Consumer<String> c = str -> System.out.println(str);

Lambda expressions can be used in:

  • A variable declaration
  • An assignment
  • A return statement
  • An array initializer
  • As a method or constructor arguments
  • A ternary conditional expression
  • A cast expression

2. Lambda Expression and Functional Interface

To have the possibility to use a lambda expression, we first need a functional interface. For example, there is a Flyable interface:

interface Flyable {
    void fly(String name);
}

We can create a lambda expression that takes a String object as an argument and returns a void:

Flyable f = s -> System.out.println(s + "is moving!");

In the other words, the lambda must take the signature of the functional interface abstract method or a compiler error will be generated.

In this case, the compiler inferred that the lambda expression can be assigned to a Flyable interface, just by its signature.

Lambda expressions don't contain information about which functional interface they are implementing. The type of the expression is concluded from the context in which the lambda is used. 

If the lambda is used as an argument to a method, the compiler will use the definition of the method to infer the type of the expression:

public class SomeClass {
    public void test() {
        move(s -> System.out.println(s + "is moving!"));
    }

    private void move(Flyable f) {
        // ...
    }
}

According to this, the same lambda expression can be associated with different functional interfaces if they have a compatible abstract method signature. For example:

interface Flyable {
    void fly(String name);
}

interface Swimable {
    void swim(String name);
}
...
Flyable f = s -> System.out.println(s + "is moving!");
Swimable swimable = s -> System.out.println(s + "is moving!");

3. Replacing Anonymous Inner Classes with Lambda Expressions

A typical Java Swing application uses anonymous inner classes to add functionality to application constructs. The problem is that inner classes can be difficult to follow. From Java 8, anonymous inner classes can be substituted with lambda expressions, which makes the code clearer.

Let's look at a typical anonymous inner class implementation for a button action implementation:

Example 3.1. Anonymous Inner Class Implementation

In this example, it is created a new object that provides an implementation of the ActionListener interface. This interface has a single method, actionPerformed, which is called by the button instance when a user actually clicks the on-screen button. The anonymous inner class provides the implementation of this method.

JButton button = ...
JLabel comp = ...

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        comp.setText("Button was clicked.");
    }
});

In fact, we do not want to pass in an object to addActionListener method. What we really want to do is passing in some behavior.

Let's rewrite this code example as a lambda expression:

Example 3.2. Lambda Expression Implementation

Instead of passing in an object that implements an interface, we are passing in a block of code - a function without a name. The code became more understandable:

JButton button = ...
JLabel comp = ...

button.addActionListener(e -> comp.setText("Button was clicked."));

Another difference between this example and the anonymous inner class is how the variable "e" is declared. Previously, we needed to explicitly provide its type - ActionEvent e. With lambda expression, it isn't required to provide the type at all, yet this example still compiles. What is happening under the hood is that the Java compiler is inferring the type of the variable "e" from its context - here, from the signature of addActionListener. It means that you do not need to explicitly write out the type when it is obvious.

Читайте также:
Комментарии