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:
MyInterfacedefines a single abstract method.- A reference to
MyInterfaceis declared but not instantiated directly. - The lambda expression supplies the implementation for
getPiValue(). - Invoking
ref.getPiValue()triggers the lambda body.
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
- Mastering Java Interfaces: Concepts, Implementation, and Best Practices
- Java Collections Framework: Core Interfaces, Implementations, and Practical Usage
- Java Collection Interface: Core Concepts & Essential Methods
- Mastering Java’s Queue Interface: Methods, Implementations, and Practical Use
- Mastering Java's Deque Interface: Features, Methods, and Practical Examples
- Java Map Interface – Comprehensive Guide to Map, Its Implementations, and Key Methods
- Java SortedMap Interface: Overview, Methods, and TreeMap Implementation
- Mastering Java NavigableMap: Features, Methods, and TreeMap Implementation
- Mastering Java’s Set Interface: Concepts, Methods, and Practical Examples
- Mastering Java 8 Lambda Expressions: A Guide to Functional Programming