Java Lambda Expressions –
Lambda Expressions were introduced in Java SE 8 to bring the principles of functional programming into the Java language. They provide a way to represent functions as objects, which makes code more concise and expressive compared to anonymous inner classes.
At their core, lambda expressions implement functional interfaces, i.e., interfaces with a single abstract method. This allows developers to write behavior directly where it is required, instead of creating a separate class.
Key Features of Lambda Expressions
- Functional Interface Implementation
A lambda expression can be used only where a functional interface is expected. A functional interface is an interface with exactly one abstract method (for example, Runnable, Comparator, Callable). - Code as Data
Functions can be treated as data, which means they can be passed as arguments, stored in variables, and executed later. - Reduction of Boilerplate Code
Before Java 8, developers often used anonymous inner classes, which required multiple lines of code. Lambda expressions reduce this verbosity. - Improved Readability and Maintainability
With a cleaner syntax, code becomes easier to read and maintain.
Syntax of Lambda Expressions
(parameters) -> { body }
Components
- Parameters – Input to the lambda expression.
- Arrow Token (->) – Separates parameters from the body.
- Body – The actual logic to be executed.
Variations in Syntax
No parameter:
() -> System.out.println("Hello");
Single parameter (parentheses optional if type is inferred):
n -> n * n;
Multiple parameters:
(a, b) -> a + b;
With code block:
(a, b) -> { int sum = a + b; return sum; }
Types of Lambda Expressions
Lambda expressions can be classified based on the number of parameters they accept. In Java, parameters can be zero, one, or multiple. The body of the lambda can either perform an action or return a value, depending on the functional interface used.
1. Lambda Expression with Zero Parameters
- This type of lambda expression does not take any input argument.
- It is useful when you want to execute a block of code that always performs the same action, regardless of input.
- The corresponding functional interface should have a method with no parameters.
- A common use case is when you want to create tasks for threads (Runnable) or when an event occurs but no input is required.
Example
@FunctionalInterface
interface ZeroParameter {
void display();
}
public class Demo {
public static void main(String[] args) {
ZeroParameter obj = () -> System.out.println("Zero-parameter Lambda Expression");
obj.display();
}
}
Output
Zero-parameter Lambda Expression
2. Lambda Expression with a Single Parameter
- This type of lambda takes exactly one argument.
- Parentheses around the parameter are optional if the compiler can infer the type.
- This form is useful for operations on collections, where each element is processed one at a time (for example, printing values, filtering, or transforming them).
- It is often used with functional interfaces like Predicate, Consumer, and Function.
Example
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList numbers = new ArrayList<>();
numbers.add(1); numbers.add(2); numbers.add(3); numbers.add(4);
// Print all numbers
numbers.forEach(n -> System.out.println("Number: " + n));
// Print even numbers
numbers.forEach(n -> {
if (n % 2 == 0) {
System.out.println("Even: " + n);
}
});
}
}
Output
Number: 1
Number: 2
Number: 3
Number: 4
Even: 2
Even: 4
3. Lambda Expression with Multiple Parameters
- This type of lambda expression accepts two or more input parameters.
- All parameters must either have their types declared explicitly or be inferred by the compiler. You cannot mix explicit and inferred parameter types in the same lambda.
- Multiple parameter lambdas are widely used in mathematical operations (addition, multiplication), comparisons (greater/lesser), or when working with Comparator for sorting objects.
- The functional interface in this case must have a method that accepts multiple arguments. For example, BiFunction or Comparator.
Key Rules:
- If the body has a single statement, braces {} are optional.
- If the body has multiple statements, you must use braces {} and a return statement if a value is returned.
- You must declare parameter types consistently (either omit for all, or specify for all).
Example: Arithmetic Operations
@FunctionalInterface
interface Operation {
int calculate(int a, int b);
}
public class Demo {
public static void main(String[] args) {
// Lambda to add two numbers
Operation add = (a, b) -> a + b;
// Lambda to multiply two numbers
Operation multiply = (a, b) -> a * b;
// Lambda with multiple statements
Operation subtract = (a, b) -> {
System.out.println("Subtracting " + b + " from " + a);
return a - b;
};
System.out.println("Sum: " + add.calculate(5, 3));
System.out.println("Product: " + multiply.calculate(5, 3));
System.out.println("Difference: " + subtract.calculate(10, 4));
}
}
Output
Sum: 8
Product: 15
Subtracting 4 from 10
Difference: 6
Example: Sorting with Comparator
import java.util.*;
public class Demo {
public static void main(String[] args) {
List names = Arrays.asList("John", "Alice", "Bob");
// Sort using lambda with multiple parameters
Collections.sort(names, (s1, s2) -> s1.compareToIgnoreCase(s2));
System.out.println("Sorted names: " + names);
}
}
Output
Sorted names: [Alice, Bob, John]
Summary of Types
Type of Lambda | Parameters | Example | Use Case |
---|---|---|---|
Zero Parameter | None | () -> System.out.println("Hi") | Runnable, event handling |
Single Parameter | One | x -> x * x | Iterating lists, filtering |
Multiple Parameters | Two or more | (a, b) -> a + b | Comparisons, arithmetic, sorting |
Commonly Used Built-in Functional Interfaces
Java provides several predefined functional interfaces in the java.util.function package.
Predicate
Represents a condition that takes one argument and returns true or false.Predicate isEven = n -> n % 2 == 0; System.out.println(isEven.test(10)); // true
Function
Represents a function that takes an argument of type T and returns a result of type R.Function convert = n -> "Number: " + n; System.out.println(convert.apply(5)); // Number: 5
Consumer
Represents an operation that accepts a single argument and returns no result.Consumer print = s -> System.out.println(s); print.accept("Hello Consumer");
Supplier
Represents a supplier of results, without any input parameters.Supplier randomValue = () -> Math.random(); System.out.println(randomValue.get());
Comparator
Used to compare two objects.Comparator compare = (a, b) -> a - b; System.out.println(compare.compare(5, 3)); // 2
Valid and Invalid Lambda Expressions
Let us evaluate some expressions:
- () -> {}; → Valid (no parameters, empty body).
- () -> "Hello"; → Valid (returns a string directly).
- () -> { return "Hello"; }; → Valid (return inside a block).
- (Integer i) -> { return "Test" + i; }; → Valid (parameter with type).
- (String s) -> { return "Test"; }; → Valid (parameter unused, still correct).
- () -> { return "Hello" } → Invalid (missing semicolon inside block).
- x -> { return x + 1; } → Context-dependent. Invalid if the type of x cannot be inferred.
- (int x, y) -> x + y → Invalid. If one parameter has an explicit type, all must have explicit types.
Advantages of Lambda Expressions
- More concise and readable code.
- Eliminates unnecessary anonymous inner classes.
- Enables functional programming in Java.
- Used extensively in Streams API for operations like map(), filter(), and forEach().
- Improves productivity by reducing boilerplate code.
Conclusion
Lambda expressions are one of the most important features introduced in Java 8. They allow developers to write clean, concise, and flexible code by implementing functional interfaces directly. They form the foundation for many modern Java features, including the Streams API and functional programming style.
Mastering lambda expressions is essential for writing efficient Java programs and is a stepping stone towards advanced features like Streams, Optional, and reactive programming.