English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Java Basic Tutorial

Java Flow Control

Java Array

Java Object-Oriented (I)

Java Object-Oriented (II)

Java Object-Oriented (III)

Java Exception Handling

Java List

Java Queue (queue)

Java Map collection

Java Set collection

Java Input/Output (I/O)

Java Reader/Writer

Other Java topics

Java Lambda Expressions

In this article, we will understand Java lambda expressions through examples, as well as the use of lambda expressions with function interfaces, generic function interfaces, and stream APIs.

Lambda expressions are in Java 8It was first introduced in. Its main purpose is to improve the expressiveness of the language.

However, before learning lambda, we first need to understand functional interfaces.

What is a functional interface?

If a Java interface contains only one abstract method, it is called a functional interface. Only this method specifies the expected use of the interface.

For example, the Runnable interface in the package java.lang is a functional interface because it consists of only one method, that is, run().

Example1: Define functional interfaces in java

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

In the above example, the interface MyInterface has only one abstract method getValue(). Therefore, it is a functional interface.

In the above example, the interface MyInterface has only one abstract method getValue(). Therefore, it is a functional interface. The annotation @FunctionalInterface is used to force the Java compiler to indicate that the interface is a functional interface. Therefore, it is not allowed to have multiple abstract methods. However, it is not mandatory.

In Java 7In it, functional interfaces are consideredSingle Abstract Method (SAM)Type. In Java 7In it, the SAM type is usually implemented through anonymous classes.

Example2: Implementing SAM with anonymous classes in Java

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

Output:

I just implemented the Runnable functional interface.

Here, we can pass an anonymous class to a method. This helps in using Java 7Write less code programs. However, the syntax is still difficult and requires a large number of additional code lines.

Java 8Further extends the functionality of SAM. Since we know that functional interfaces have only one method, there is no need to define the name of the method when passing it as a parameter. Lambda expressions allow us to do this.

Introduction to Lambda Expressions

Lambda expressions are essentially anonymous or unnamed methods. Lambda expressions cannot be executed independently. Instead, they are used to implement methods defined by functional interfaces.

How to define Lambda expressions in Java?

This is how we define lambda expressions in Java.

(parameter list) -> lambda body

using the new operator (->) known as the arrow operator or lambda operator. Let's explore some examples,

Suppose we have a method like this:

double getPiValue() {
    return 3.1415;
}

We can write this method using a lambda expression as follows:

() -> 3.1415

Here, the method has no parameters. Therefore, the left side of the operator includes an empty parameter. The right side is the lambda body, which specifies the operation of the lambda expression. In this case, it will return a value3.1415。

Types of Lambda Bodies

In Java, there are two types of lambda bodies.

1. Single expression body

() -> System.out.println("Lambdas are great");

This type of lambda body is called an expression body.

2. The body composed of code blocks.

() -> {
    double pi = 3.1415;
    return pi;
};

This type of lambda body is called a block body. The block body allows the lambda body to contain multiple statements. These statements are enclosed in parentheses, and you must add a semicolon after the parentheses.

Note:For block bodies, you should always have a return statement. However, a single expression body does not require a return statement.

Example3:Lambda Expressions

Let's write a Java program that uses lambda expressions to return the value of Pi.

As mentioned before, lambda expressions are not executed independently. Instead, they form the implementation of the abstract methods defined by the functional interface.

Therefore, we need to define a functional interface first.

import java.lang.FunctionalInterface;
//This is a functional interface
@FunctionalInterface
interface MyInterface{
    // Abstract method
    double getPiValue();
}
public class Main {
    public static void main(String[] args) {
    //Declare a reference to MyInterface
    MyInterface ref;
    
    // Lambda expression
    ref = () -> 3.1415;
    
    System.out.println("Pi = ", + ref.getPiValue());
    } 
}

Output:

Pi = 3.1415

In the above example,

  • We created a functional interface named MyInterface. It contains an abstract method named getPiValue().

  • Inside the Main class, we declare a reference to MyInterface. Please note that we can declare a reference to an interface, but we cannot instantiate an interface. That is because,
     

    //It will throw an error
    MyInterface ref = new myInterface();
    // This is valid
    MyInterface ref;
  • Then, we assigned a lambda expression to the reference.

    ref = () -> 3.1415;
  • Finally, we use the reference interface to call the method getPiValue().

    System.out.println("Pi = ", + ref.getPiValue());

Parameterized Lambda Expressions

So far, we have created lambda expressions without any parameters. However, like methods, lambda expressions can also have parameters. For example,

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

Here, the variable n in the parentheses is the parameter passed to the lambda expression. The lambda body accepts the parameter and checks if it is even or odd.

Example4:Use the lambda expression with the parameter

@FunctionalInterface
interface MyInterface {
    //Abstract method
    String reverse(String n);
}
public class Main {
    public static void main(String[] args) {
                //Declare a reference to MyInterface
                //Assign the lambda expression to the reference
        MyInterface ref = (str) -> {
            String result = "";
            for (int i = str.length();-1; i >= 0; i--){
                result += str.charAt(i);
            }
            
            return result;
        };
        //Call the method of the interface
        System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
    }
}

Output:

Lambda reversed = adbmaL

Generic functional interface

So far, we have used functional interfaces that accept only one type of value. For example,

@FunctionalInterface
interface MyInterface {
    String reverseString(String n);
}

The functional interface above only accepts String and returns String. But we can make the functional interface generic so that it can accept any data type. If you are not familiar with generics, please visitJava generics

Example5:Generic functional interface and Lambda expression

// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {
    // Generic method
    T func(T t);
}
// GenericLambda.java
public class Main {
    public static void main(String[] args) {
                //Declare a reference to GenericInterface
                // GenericInterface operates on String data
                //Allocate a lambda expression for it
        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"));
                //Declare another reference to GenericInterface
                // GenericInterface operates on integer data
                //Allocate a lambda expression for it
        GenericInterface<Integer> factorial = (n) -> {
            int result = 1;
            for (int i = 1; i <= n; i++)
            result = i * result;
            return result;
        };
        System.out.println("5factorial = " + factorial.func(5));
    }
}

Output:

Lambda reversed = adbmaL
5factorial = 120

In the above example, we created a generic functional interface named GenericInterface. It contains a generic method named func().

Inside the class:

  • GenericInterface<String> reverse - Create a reference to this interface. Now, the interface can handle String type data.

  • GenericInterface<Integer> factorial -Create a reference to this interface. In this case, the interface operates on Integer type data.

Lambda expressions and Stream API

newjava.util.streampackage has been added to JDK8It allows Java developers to perform search, filter, map, reduce, and other operations, or operate on lists and other collections.

For example, we have a data stream (in our example, a list of strings), where each string is a country name and country/Combination of regions. Now, we can process this data stream and retrieve locations only from Nepal.

For this, we can combine the use of Stream API and Lambda expressions to perform batch operations on the stream.

Example6: Demonstrate the use of lambda with Stream API

import java.util.ArrayList;
import java.util.List;
public class StreamMain {
    //Use ArrayList to create a list object
    static List<String> places = new ArrayList<>();
    //Prepare our data
    public static List getPlaces(){
        //Add locations and countries to the list
        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:");
        
        myPlaces.stream()
                .filter((p -> p.startsWith("Nepal"))
                .map((p -> p.toUpperCase())
                .sorted()
                .forEach((p -> System.out.println(p));
    }
}

Output:

Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA

In the above example, please note the following statements:

myPlaces.stream()
        .filter((p -> p.startsWith("Nepal"))
        .map((p -> p.toUpperCase())
        .sorted()
        .forEach((p -> System.out.println(p));

Here, we use methods such as filter(), map(), and forEach() from the Stream API. These methods can take lambda expressions as input.

We can also define our own expressions based on the grammar we have learned above. As shown in the example above, this allows us to greatly reduce the number of lines of code.