Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Industrial programming >> Java

Mastering Java Lambda Expressions: From Basics to Stream API

Mastering Java Lambda Expressions

Explore Java’s lambda expressions, functional interfaces, generics, and the Stream API with clear examples.

Java 8 introduced lambda expressions to enhance the expressive power of the language, enabling developers to write more concise and readable code.

Before diving into lambdas, it’s essential to understand functional interfaces, the backbone of lambda implementation.


What is a Functional Interface?

A functional interface is a Java interface that contains exactly one abstract method. This single method defines the interface’s intended purpose.

The Runnable interface from java.lang is a classic example, exposing only the run() method.

Example 1: Defining a Functional Interface

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

Here, MyInterface is annotated with @FunctionalInterface, which compels the compiler to enforce the functional interface contract, preventing accidental addition of multiple abstract methods. The annotation is optional but highly recommended for clarity and safety.

In Java 7, functional interfaces were referred to as Single Abstract Methods (SAMs) and were typically implemented using anonymous classes.

Example 2: Implementing a SAM with an Anonymous Class

public class FunctionInterfaceTest {
    public static void main(String[] args) {
        // Anonymous class implementing Runnable
        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.

While anonymous classes reduce boilerplate compared to traditional implementations, the syntax remains verbose. Lambda expressions eliminate the need for explicit method names when passing functional interfaces as arguments.


Introduction to Lambda Expressions

A lambda expression is essentially an anonymous method that provides a concise implementation of a functional interface’s abstract method. It doesn’t execute independently; instead, it supplies the behavior for the interface’s method.

How to Define a Lambda Expression in Java

The general syntax is:

(parameter list) -> lambda body

The arrow operator (->) separates the parameter list from the body. Below are concrete examples.

Suppose we have a method returning Pi:

double getPiValue() {
    return 3.1415;
}

Using a lambda:

() -> 3.1415

With no parameters, the left side of the arrow is empty, and the right side is the expression body that returns the value.


Types of Lambda Bodies

Java lambda bodies come in two forms:

1. Expression Body – contains a single expression.

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

2. Block Body – encapsulated in braces, allowing multiple statements and an optional return.

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

Note: Expression bodies don’t require an explicit return, whereas block bodies do if a value is returned.


Example 3: Lambda Expression in Practice

Let’s write a Java program that returns Pi using a lambda expression.

import java.lang.FunctionalInterface;

@FunctionalInterface
interface MyInterface {
    double getPiValue();
}

public class Main {
    public static void main(String[] args) {
        MyInterface ref = () -> 3.1415;
        System.out.println("Value of Pi = " + ref.getPiValue());
    }
}

Output:

Value of Pi = 3.1415

Key takeaways:


Lambda Expressions with Parameters

Like methods, lambdas can accept parameters. Example:

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

This lambda checks if the input integer is even.

Example 4: Reversing a String with a Parameterized Lambda

@FunctionalInterface
interface MyInterface {
    String reverse(String s);
}

public class Main {
    public static void main(String[] args) {
        MyInterface ref = (str) -> {
            String result = "";
            for (int i = str.length() - 1; i >= 0; i--) {
                result += str.charAt(i);
            }
            return result;
        };
        System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
    }
}

Output:

Lambda reversed = adbmaL

Generic Functional Interface

Functional interfaces can be generic, allowing them to operate on any data type. For instance:

@FunctionalInterface
interface MyInterface {
    T func(T t);
}

Example 5: Generic Lambdas for Strings and Integers

public class Main {
    public static void main(String[] args) {
        // String reversal
        MyInterface 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"));

        // Integer factorial
        MyInterface factorial = (n) -> {
            int result = 1;
            for (int i = 1; i <= n; i++) {
                result *= i;
            }
            return result;
        };
        System.out.println("factorial of 5 = " + factorial.func(5));
    }
}

Output:

Lambda reversed = adbmaL
factorial of 5 = 120

Lambda Expressions and the Stream API

The java.util.stream package, introduced in JDK 8, empowers developers to perform functional-style operations on collections: filtering, mapping, reducing, sorting, and more.

Consider a list of place names, each prefixed by its country. Using streams and lambdas, we can efficiently extract and manipulate data.

Example 6: Stream Processing with Lambdas

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

public class StreamMain {
    static List places = new ArrayList<>();

    public static List getPlaces() {
        places.add("Nepa, Kathmandu");
        places.add("Nepa, Pokhara");
        places.add("India, Delhi");
        places.add("USA, New York");
        places.add("Africa, Nigeria");
        return places;
    }

    public static void main(String[] args) {
        List myPlaces = getPlaces();
        System.out.println("Places from Nepal:");
        myPlaces.stream()
                .filter(p -> p.startsWith("Nepa"))
                .map(p -> p.toUpperCase())
                .sorted()
                .forEach(p -> System.out.println(p));
    }
}

Output:

Places from Nepal:
NEPA, KATHMANDU
NEPA, POKHARA

Here, the chain of stream operations—filter, map, sorted, forEach—each accepts a lambda, dramatically reducing boilerplate compared to pre‑Java‑8 solutions.


Java

  1. Mastering Java Interfaces: Concepts, Implementation, and Best Practices
  2. Java Collections Framework: Core Interfaces, Implementations, and Practical Usage
  3. Java Collection Interface: Core Concepts & Essential Methods
  4. Mastering Java’s Queue Interface: Methods, Implementations, and Practical Use
  5. Mastering Java's Deque Interface: Features, Methods, and Practical Examples
  6. Java Map Interface – Comprehensive Guide to Map, Its Implementations, and Key Methods
  7. Java SortedMap Interface: Overview, Methods, and TreeMap Implementation
  8. Mastering Java NavigableMap: Features, Methods, and TreeMap Implementation
  9. Mastering Java’s Set Interface: Concepts, Methods, and Practical Examples
  10. Mastering Java 8 Lambda Expressions: A Guide to Functional Programming