Lambda expressions were introduced in Java 8 as a way to implement functional programming in Java. They allow you to write more concise, readable, and flexible code, particularly when working with functional interfaces.
In this tutorial, we’ll explore what lambda expressions are, how they work, and look at several code examples.
Table of Contents
Table of Contents
What is a Lambda Expression?
A lambda expression is essentially a short block of code that takes in parameters and returns a value. It is used to provide the implementation of a method defined by a functional interface.
Syntax of Lambda Expression:
(parameters) -> expression // OR (parameters) -> { statements }
Parameters: The input values for the lambda expression.
Arrow (->): Separates the parameters from the body of the lambda expression.
Expression or Statements: The block of code that is executed when the lambda is invoked.
Functional Interface
A functional interface is an interface with a single abstract method. Lambda expressions can only be used to implement functional interfaces.
Example of a functional interface:
@FunctionalInterface interface MyFunctionalInterface { void sayMessage(String message); }
Example 1: Basic Lambda Expression
Without lambda expressions, you would typically use an anonymous class to provide the implementation of an interface.
Before Lambda:
MyFunctionalInterface message = new MyFunctionalInterface() { @Override public void sayMessage(String msg) { System.out.println("Message: " + msg); } }; message.sayMessage("Hello, Java Lambda!");
Using Lambda:
MyFunctionalInterface message = (msg) -> System.out.println("Message: " + msg); message.sayMessage("Hello, Java Lambda!");
Explanation:
Here, (msg) is the parameter.
System.out.println(“Message: ” + msg); is the body of the lambda expression.
No need for the boilerplate of creating an anonymous class.
Example 2: Lambda Expression with Multiple Parameters
Suppose we have a functional interface for mathematical operations:
@FunctionalInterface interface MathOperation { int operation(int a, int b); }
We can implement this interface using lambda expressions for different operations:
Addition:
MathOperation add = (a, b) -> a + b; System.out.println("Addition: " + add.operation(5, 3)); // Output: 8
Subtraction:
MathOperation subtract = (a, b) -> a - b; System.out.println("Subtraction: " + subtract.operation(5, 3)); // Output: 2
Multiplication:
MathOperation multiply = (a, b) -> a * b; System.out.println("Multiplication: " + multiply.operation(5, 3)); // Output: 15
Division:
MathOperation divide = (a, b) -> a / b; System.out.println("Division: " + divide.operation(6, 3)); // Output: 2
Explanation:
(a, b) are the input parameters.
The return value is the result of performing the mathematical operation.
Example 3: Using Lambda with Collections
One of the most common use cases for lambda expressions is with Java collections, especially when sorting or filtering elements.
Sorting a List using Lambda Expression:
import java.util.*; public class LambdaWithList { public static void main(String[] args) { List names = Arrays.asList("John", "Alice", "Bob", "Eve"); // Sort using lambda expression Collections.sort(names, (a, b) -> a.compareTo(b)); System.out.println(names); // Output: [Alice, Bob, Eve, John] } }
Explanation:
We use Collections.sort() and provide a lambda expression (a, b) -> a.compareTo(b) as a comparator.
Example 4: Filtering a List using Lambda Expression and Streams
Lambda expressions are frequently used in combination with Streams for performing operations on collections.
Filtering and Printing Even Numbers from a List:
import java.util.*; import java.util.stream.Collectors; public class LambdaWithStreams { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Filter even numbers and print them List evenNumbers = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(evenNumbers); // Output: [2, 4, 6, 8, 10] } }
Explanation:
filter(n -> n % 2 == 0) is the lambda expression that checks if a number is even.
stream() is used to create a stream from the list.
The filtered numbers are collected back into a list using collect().
Example 5: Runnable Interface with Lambda
The Runnable interface in Java has a single method, so it is a functional interface, and we can use a lambda expression to provide its implementation.
Using Lambda with Runnable:
public class LambdaRunnable { public static void main(String[] args) { // Traditional Runnable implementation Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Runnable using anonymous class"); } }; // Lambda implementation of Runnable Runnable r2 = () -> System.out.println("Runnable using lambda"); // Run both r1.run(); r2.run(); } }
Explanation:
In the lambda version, we avoid the need for an anonymous inner class and directly implement the run() method with the lambda expression () -> System.out.println(“Runnable using lambda”).
Example 6: Using Custom Functional Interface with Lambda
We can define our custom functional interface and use a lambda expression to provide its implementation.
Custom Functional Interface:
@FunctionalInterface interface Greet { void sayHello(String name); }
Implementing the Interface with Lambda:
public class LambdaCustomInterface { public static void main(String[] args) { Greet greet = (name) -> System.out.println("Hello, " + name); greet.sayHello("Java Developer"); } }
Explanation:
The Greet interface has one abstract method, so it can be implemented using a lambda expression (name) -> System.out.println(“Hello, ” + name).
Example 7: Lambda Expressions with Consumer, Supplier, and Predicate
Java has built-in functional interfaces in the java.util.function package. Three commonly used ones are Consumer, Supplier, and Predicate.
Consumer Example (Takes an input and performs an action):
import java.util.function.Consumer; public class LambdaConsumer { public static void main(String[] args) { Consumer consumer = (s) -> System.out.println("Consumed: " + s); consumer.accept("Java Lambda"); } }
Supplier Example (Returns a value):
import java.util.function.Supplier; public class LambdaSupplier { public static void main(String[] args) { Supplier supplier = () -> Math.random(); System.out.println("Random number: " + supplier.get()); } }
Predicate Example (Returns a boolean result):
import java.util.function.Predicate; public class LambdaPredicate { public static void main(String[] args) { Predicate isEven = (n) -> n % 2 == 0; System.out.println("Is 4 even? " + isEven.test(4)); // Output: true } }
Explanation:
Consumer takes an input and performs an action without returning a result.
Supplier provides an output without taking any input.
Predicate tests a condition and returns a boolean.
Example 8: Using Lambda in a ForEach Loop
We can use lambda expressions in combination with the forEach() method to iterate over collections.
import java.util.Arrays; import java.util.List; public class LambdaForEach { public static void main(String[] args) { List names = Arrays.asList("John", "Alice", "Bob", "Eve"); // Using lambda with forEach names.forEach(name -> System.out.println("Hello, " + name)); } }
Explanation:
The forEach() method takes a lambda expression as an argument, which will be applied to each element of the list.
Conclusion
Lambda expressions in Java offer a powerful, compact way to implement functional programming. They remove the verbosity of anonymous inner classes and make your code more concise and readable.
With the examples above, you can see how lambda expressions are used in various real-world scenarios such as sorting, filtering, and using built-in functional interfaces.
By practicing with lambda expressions, you'll become more comfortable using them, making your Java code more modern and efficient.