Java Lambda Expressions

In this article, you’ll learn about new feature introduced in Java 8, that is support for lambda expression using functional interface.

Lambda expressions was a hot topic when Java 8 was released. Lambda expressions were added in JDK version 8 to enhance the Java performance by increasing the expressive power of the language.

But, before getting into lambdas, first we need to understand what is a functional interface.


What is Functional Interface?

If a Java interface contains one and only one abstract method then it is termed as functional interface. This only one method specifies the intended purpose of the interface. For example, the Runnable interface from package java.lang; is a functional interface because it constitutes only one method i.e. run().

Example 1: Define a Functional Interface in java

import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
    double getValue();
}

Note: The @FunctionalInterface annotation is not necessary, but it is wise to use it because it forces the Java compiler to indicate that the Interface defined is a functional interface and it must have only one abstract method. In Java 7, functional interface were referred as Single Abstract Method or SAM type.

SAMs were commonly implemented with Anonymous Classes in Java 7.

Example 2: Implement SAM with anonymous classes in java

public class FunctionInterfaceTest {
  public static void main(String[] args) {
      new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("I just implemented the Runnable Functional Interface.");
          }
      }).start();
  }
}

The ability to pass an anonymous class to a constructor or method made it easier to write Java 7 code with fewer files. However, the syntax was still difficult and a lot of extra lines of code was required.

Java 8 extended the power of a SAMs by going a step further. Since we know that a functional interface has just one method, there should be no need to define the name of that method when passing it as an argument. Lambda expression allows us to do exactly that.


Introduction to lambda expression

Lambda expression is, essentially, an anonymous or unnamed method. The lambda expression do not execute on its own. Instead, it is used to implement a method defined by a functional interface.

How to define lambda expression in Java?

The lambda expression introduces a new syntax element and operator in Java language. The new operator is referred to as the lambda operator or the arrow operator (->).

Let’s write a simple method which just returns a constant double value.

double getPiValue() { return 3.1415; }

The equivalent lambda expression for above method is:

() -> 3.1415

In the lambda expression, the left side of the expression specifies any parameters required by the expression, where as the right side is the lambda body, which specifies the action of lambda expression.

The lambda body is of two types.

  1. A body with a single expression
    () -> System.out.println(“Lambdas are great”);
  2. A body that consists of a block of code.
    () -> {
        double pi = 3.1415;
        return pi;
    }

A lambda expression can also have parameters. For example:

(n) -> (n%2)==0

This lambda expression evaluates it the value of n is even or odd.

If lambda body is a code block, you must always return a value explicitly. But, if lambda body is just an expression, return statement is not required.

Let’s write some practical Java code with lambda expression which simply returns the value of Pi.

As mentioned earlier, lambda expression is not executed on its own. Rather, it forms the implementation of the abstract method defined by the functional interface.

Example 3: Define lambda expression with functional interface in java

So, let’s first define a functional interface MyInterface.java:

import java.lang.FunctionalInterface;

// This is functional interface
@FunctionalInterface
public interface MyInterface{

    double getPiValue();
}

Now, we assign the lambda expression to the instance of the functional interface.

public class LambdaMain {

    public static void main( String[] args ) {

    MyInterface myInterface;
    myInterface = () -> 3.1415;
    
    System.out.println("Value of Pi = " + myInterface.getPiValue());
    } 
}

When you run the program, the output will be:

Value of Pi = 3.1415

The lambda expression must be compatible with the abstract method. This means, if you assign () -> "3.1415" to the myInterface instance, the code is illegal and won’t work because the type of String is not compatible with the double as defined in the functional interface.

You probably wouldn't use a lambda expression like this in a real program. Let’s look at some other lambda expression example that takes in parameter.

Example 4: Using lambda expression with parameters in java

@FunctionalInterface
interface MyInterface {

    String reverse(String n);
}

public class ParamLambdaMain {

    public static void main( String[] args ) {

        MyInterface myInterface = (str) -> {

            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        };

        System.out.println("Lambda reversed = " + myInterface.reverse("Lambda"));
    }

}

When you run the program, the output will be:

Lambda reversed = adbmaL

Generic Functional Interface

The above functional interface only accepts String and returns String object. However, we can make the functional interface generic, so that any data type is accepted.

Example 5: How can Functional Interface accept any data type in java?

Let’s look at how it’s done:

// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {

    T func(T t);
}

Now, this GenericInterface is compatible with any lambda expression that takes one parameter and returns the value of the same type.

// GenericLambda.java
public class GenericLambda {

    public static void main( String[] args ) {

        GenericInterface<String> reverse = (str) -> {

            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        };

        System.out.println("Lambda reversed = " + reverse.func("Lambda"));

        GenericInterface<Integer> factorial = (n) -> {

            int result = 1;
            for (int i = 1; i <= n; i++)
            result = i * result;
            return result;
        };

        System.out.println("factorial of 5 = " + factorial.func(5));
    }
}

When you run the program, the output will be:

Lambda reversed = adbmaL
factorial of 5 = 120

Lambda Expression and Stream API

The new java.util.stream package has been added to JDK8 which allows java developers to perform operations like search, filter, map, reduce, or otherwise manipulate collections like Lists.

For example, we have a stream of data (in our case a List of String) where each string is combination of country name and place of the country. Now, we can process this stream of data and retrieve only the places from Nepal. We can perform bulk operation in the stream by the combination of Stream API and Lambda expression.

Example 6: Demonstration of using lambdas with the Stream API

import java.util.ArrayList;
import java.util.List;

public class StreamMain {

    static List<String> places = new ArrayList<>();

    // preparing our data
    public static List getPlaces(){

        places.add("Nepal, Kathmandu");
        places.add("Nepal, Pokhara");
        places.add("India, Delhi");
        places.add("USA, New York");
        places.add("Africa, Nigeria");

        return places;
    }

    public static void main( String[] args ) {

        List<String> myPlaces = getPlaces();
        System.out.println("Places from Nepal:");
        
        // Filter places from Nepal
        myPlaces.stream()
                .filter((p) -> p.startsWith("Nepal"))
                .map((p) -> p.toUpperCase())
                .sorted()
                .forEach((p) -> System.out.println(p));
    }

}

When you run the program, the output will be:

Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA

The stream API gives us access to methods like filter(), map() and forEach() that can take a lambda expression as input. We can use both Java's inbuilt methods and also define our own expressions based on the syntax we learned above. This allows us to reduce the lines of code drastically as we saw in the above example.