Introduction to Lambda Expression in Java


Reading time: 35 minutes | Coding time: 10 minutes

Lambda Expression is one of the newest features introduced in Java 8 release. As the name suggest it is an expression but in the form of an anonymous function definition. Let's dive deep into it.

In Java, all functions are bound to either a class or an interface as instance methods or static methods or default methods. We cannot define a function outside of a class. But the introduction of Lambda Expression also called anonymous function (functions without a name) helps us to define a function that does not belong to a class.

Type of a Lambda Expression

Like every other expression, a Lambda Expression evaluates to a single value which will have a type associated with it. But what would be the type of a lambda expression? The answer is Functional Interfce. Before discussing Lambda Expression further, let's see what a Functional Interface is.

Functional Interface

A Functional Interface is an interface that can have at most one abstract method. But there are no other restrictions on having or not having any number of default or static method definitions along with the single abstract method.

@FunctionalInterface
interface FunctionalInterface{
    void abstractMethod(); // single abstract method
    
    //definitions of default and static methods if needed
    void default defaultMethod(){
    ...
    }
    ...
}

In order to distinguish between normal interface and Functional Interface, you can annotate a Functional Interface with the annotation @FunctionalInterface. It is not mandatory you annotate a Functional Interface, but it is recommended as a best practice to annotate so that accidental abstract method rule violation can be detected during compile time.

Putting it all together

Let's come back to the type of a Lambda Expression.

A Functional Interface can be the target type of a Lambda Expression by satisfying the condition that, the Lambda Expression must be an anonymous implementation of the abstract in a Functional Interface.

This may seem surprising to you. Get it clarified here. A Lambda Expression also called anonymous function is exactly a function definition with parameters, function body, and return type but without a function name. However, in order to consider this anonymous function as a valid Lambda Expression, it must be an implementation of the abstract method in any of the existing Functional Interface. Whenever the compiler encounters a Lambda Expression, it will check for the type, also called the target type of the expression which must be a Functional Interface. The compiler determines the target type by examining the context or the situation in which the expression is defined; the context may be a variable assignment, a return statement, method or constructor arguments and so on. If the context is a variable being assigned with a Lambda Expression, then the compiler expects the target type as the type of the variable, which must be the corresponding Functional Interface. Let's get into action now.

The definition of a Lambda Expression is a bit different from that of a normal function definition. A Lambda Expression consists of a comma-separated list of parameters, arrow token " -> ", and the function body. See the syntax and some samples below.

//Syntax:
(parameter list) -> {function body}

//Examples:
() -> System.out.println("Hello world"); 
(a, b) -> a+b;

Consider the Lambda Expression () -> System.out.println("Hello world");
This is the implementation of an abstract method which takes no argument and return type as void. We can define a Functional Interfce containing the abstract method that matches the defined Lambda Expression.

@FunctionalInterface
interface HelloWorld{
       void printHelloWorld();
}

We have defined a Functional Interface HelloWorld. If a class implements this interface, then the class must give implementation to the abstract method printHelloWorld as shown below.

public class LambdaSample implements HelloWorld{

    public static void main(String[] args){
           new LambdaSample().printHelloWorld();
    }
    
    void printHelloWorld(){
        System.out.println("HelloWorld");
    }
}

Using a Lambda Expression we can further simplify the code and the explicit implementation of the interface can also be removed. Have a look at the modified code below.

public class LambdaSample {

    public static void main(String[] args){
           HelloWorld helloWorld = () -> System.out.println("Hello world"); // Lamda expression assignment to a variable of matching functional interface
           helloWorld.printHelloWorld(); // excecution of lambda expression by invoking functional interfce abstract method.
    }
    
}

Here the Lambda Expression matches the printHelloWorld abstract method of the given functional interface HelloWorld. Therefore a variable of type HelloWorld became the target type and can now refer to the Lambda Expression and using that variable we can execute the lambda expression by invoking interface abstract method.

I hope the connection between Functional Interface and the Lambda Expression is very well cleared to you. One main point that always needs to keep in mind is that Lambda Expression can only be used if the target type is specified; there must be a context from which the target type can be determined.

Type Inference

A Lambda Expression can have one or more arguments like the examples below.

(int a, int b) -> a+b;
(String s) -> System.out.println(s);

As in the examples above you dont need to strictly type the type of arguments; type of arguments is completely optional. You can define with variable names alone. For example, the above first line of code can be modified as shown below.

(a, b) -> a+b;

When you specify a Functional Interface as the target type of a Lambda Expression, the compiler will infer the parameter type from the abstract method declaration. Suppose for the above Lambda Expression the matching abstract method compiler found was

int add(int a, int b);

From this abstract method, the compiler can infer that both parameters are of type int.

java.lang.Runnable

java.lang package contains a well known interface Runnable which is a Functional Interface with abstract method run. run method is similar to the above-defined printHelloWorld with void as return type and empty parameter list. So whenever you need to define similar Lambda Expressions you can avoid defining custom interfaces, rather you can define the target type as Runnable. In the above code, we can replace the interface HelloWorld with Runnable as shown below.

//Specifying the target type as Runnable
Runnable runnable = () -> System.out.println("Hello World");

//Excecution of Lambda Expression
runnable.run();

Similar to Runnable java.util.function package contains definitions of mutiple standard Functional Interfaces. Before definig custom Functional Interface, you can check this package and find an appropriate one that satisfies your requirement.

Generic Functional Interface

If you need to use generics in abstract method, then you can define Generic Functional Interface. Suppose your requirement is to define an abstract method that will have two generic arguments and returns a generic type, then a sample Generic Functional Interface defined as follows.

@FunctionalInterface
interface GenericOperation<T>{
    T result(T arg1, T arg2);
}

For this interface, we can give multiple implementations in the form of a Lambda Expression.Lets define addition operation for Integer and String types.

//Addition of two Integer types
GenericOperation<Integer> integerAddition = (a, b) -> a+b; 

//Addition of two String types
GenericOperation<String> stringAddition = (a, b) -> a+" "+b;

//invoking the lambda expressions
integerAddition.result(2,4);
stringAddition.result("Hello", "world");

Let's wind up this article by listing some important points.

Important Points on Lambda Expressions

  • Parameter list and return type of a Lambda Expression must match that of the abstract method in the Functional Interface.
  • Parameter list can be either empty or can have one or more arguments.
  • Function body may contain one or more statements.
  • If there is only one statement in the function body, then you can omit the parenthesis around it. If the body contains more statements then make them a block of statements by wrapping in parenthesis.
  • Like a normal function, Lambda Expression can have a return statement.
  • If the function body contains a single statement without enclosing in parathesis then, the explicit return keyword is not needed; implicitly the Lambda Expression returns that single statement.
  • If the function body is enclosed in parathesis and you want to return something from that function, then give explicit return statement.

See some sample valid Lambda Expressions below,based on the above mentioned points.

() -> System.out.println("Hello world"); 
(a, b) -> a+b;
(s) -> System.out.println(s);
(a, b) -> {return a+b;};
(a, b) -> {
         System.out.println("Result is "+a+b);
         return a+b;
         }                 

References