Java 8 Lambda Expressions and Functional Interfaces

RMAG news

Overview :

Java 8 introduced Lambda Expressions and Functional Interfaces, which bring functional programming capabilities to Java. These features allow for more concise and readable code, especially when working with collections and performing common tasks like filtering, mapping, and reducing

By the end of this blog, we will understand:

What Lambda Expressions are?
What Functional Interfaces are?
How to create and use Lambda Expressions?
Common use cases for Lambda Expressions and Functional Interfaces

What are Lambda Expressions?
Lambda Expressions are a way to provide clear and concise syntax for writing anonymous methods (functions). They enable you to treat functionality as a method argument, or pass a block of code around as data

Syntax of Lambda Expressions :
The basic syntax of a lambda expression is:

(parameters) -> expression
or
(parameters) -> { statements; }

Example: Simple Lambda Expression

// Traditional way using an anonymous class
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(“Hello, world!”);
}
};

// Using a lambda expression
Runnable lambdaRunnable = () -> System.out.println(“Hello, world!”);

What are Functional Interfaces?
A Functional Interface is an interface with a single abstract method. They can have multiple default or static methods but only one abstract method. Lambda expressions can be used to instantiate functional interfaces.

Example: Defining a Functional Interface

@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}

Java 8 includes several built-in functional interfaces in the java.util.function package, such as Predicate, Function, Consumer, and Supplier.

Creating and Using Lambda Expressions :

Example 1: Using Predicate Interface
The Predicate interface represents a boolean-valued function of one argument.

import java.util.function.Predicate;

public class Main {
public static void main(String[] args) {
Predicate<String> isEmpty = (str) -> str.isEmpty();

System.out.println(isEmpty.test(“”)); // Output: true
System.out.println(isEmpty.test(“Hello”)); // Output: false
}
}

Example 2: Using Function Interface
The Function interface represents a function that takes one argument and produces a result.

import java.util.function.Function;

public class Main {
public static void main(String[] args) {
Function<Integer, String> intToString = (num) -> “Number: ” + num;

System.out.println(intToString.apply(5)); // Output: Number: 5
}
}

Example 3: Using Consumer Interface
The Consumer interface represents an operation that takes a single argument and returns no result.

import java.util.function.Consumer;

public class Main {
public static void main(String[] args) {
Consumer<String> printUpperCase = (str) -> System.out.println(str.toUpperCase());

printUpperCase.accept(“hello”); // Output: HELLO
}
}

Example 4: Using Supplier Interface
The Supplier interface represents a supplier of results, which doesn’t take any arguments and returns a result.
import java.util.function.Supplier;

public class Main {
public static void main(String[] args) {
Supplier<String> helloSupplier = () -> “Hello, world!”;

System.out.println(helloSupplier.get()); // Output: Hello, world!
}
}

Common Use Cases for Lambda Expressions and Functional Interfaces :

Use Case 1: Filtering Collections

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList(“Alice”, “Bob”, “Charlie”, “David”);

// Filter names that start with ‘A’
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith(“A”))
.collect(Collectors.toList());

filteredNames.forEach(System.out::println); // Output: Alice
}
}

Use Case 2: Mapping Collections

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList(“Alice”, “Bob”, “Charlie”, “David”);

// Convert names to uppercase
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());

upperCaseNames.forEach(System.out::println); // Output: ALICE, BOB, CHARLIE, DAVID
}
}

Use Case 3: Reducing Collections

import java.util.Arrays;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// Sum of all numbers
int sum = numbers.stream()
.reduce(0, Integer::sum);

System.out.println(“Sum: ” + sum); // Output: Sum: 15
}
}

Use Case 4: Creating Custom Functional Interfaces

@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}

public class Main {
public static void main(String[] args) {
// Define lambda expressions for addition and multiplication
MathOperation addition = (a, b) -> a + b;
MathOperation multiplication = (a, b) -> a * b;

System.out.println(“Addition: ” + addition.operate(5, 3)); // Output: Addition: 8
System.out.println(“Multiplication: ” + multiplication.operate(5, 3)); // Output: Multiplication: 15
}
}

Summary
Lambda Expressions and Functional Interfaces are powerful features in Java 8 that enable you to write more concise, readable, and functional-style code. They are particularly useful for operations on collections and data, allowing you to:

Filter: Select elements based on a condition.
Map: Transform elements.
Reduce: Combine elements into a single result.
Custom Functional Interfaces: Define your own interfaces for specific tasks

Happy Coding…