Lambda Expressions in Java
Learn how lambda expressions provide a concise way to represent behavior and work with functional interfaces, collections, streams, and modern Java APIs.
What is a Lambda Expression?
A lambda expression is a compact representation of behavior that can be passed as a value. Lambda expressions are closely associated with functional interfaces.
(parameters) -> expression
(parameters) -> {
// statements
}
Basic Lambda Expression
Runnable task = () -> {
System.out.println("CIIT Institute background task is running successfully 🏠👍...!");
};
task.run();
The lambda () -> { ... } provides the
implementation of the single abstract method of
Runnable.
Lambda Parameters
Lambda expressions can accept zero, one, or multiple parameters.
var square = (int number) ->
number * number;
System.out.println(
square.applyAsInt(5)
);
For compatible functional interfaces, parameter types can often be inferred.
Lambda and Functional Interfaces
A functional interface has exactly one abstract method. Lambda expressions can provide implementations for such interfaces.
interface Calculator {
int calculate(int a, int b);
}
Calculator addition =
(a, b) -> a + b;
System.out.println(
addition.calculate(10, 20)
);
Expression Body vs Block Body
Calculator add =
(a, b) -> a + b;
Calculator multiply =
(a, b) -> {
int result = a * b;
return result;
};
Built-in Functional Interfaces
Java provides many functional interfaces in
java.util.function.
| Interface | Purpose |
|---|---|
Predicate<T> |
Tests a condition and returns boolean. |
Function<T,R> |
Converts an input into an output. |
Consumer<T> |
Consumes an input without returning a result. |
Supplier<T> |
Supplies a value without taking an input. |
UnaryOperator<T> |
Transforms a value into the same type. |
BinaryOperator<T> |
Combines two values of the same type. |
Predicate
Predicate<T> represents a boolean-valued
function.
Predicate<Integer> isEven =
number -> number % 2 == 0;
System.out.println(
isEven.test(10)
);
Function
Function<T,R> accepts one value and produces
another value.
Function<String, Integer> length =
text -> text.length();
System.out.println(
length.apply("Java")
);
Consumer
Consumer<T> accepts a value and performs an
operation without returning a result.
Consumer<String> printer =
text -> System.out.println(text);
printer.accept("Welcome to CIIT Institute: Learn, Code, and Succeed 👀👨🏫...!");
Supplier
Supplier<T> produces a value without taking
an input.
Supplier<Double> randomValue =
() -> Math.random();
System.out.println(
randomValue.get()
);
Method References
Method references provide an even shorter syntax when an existing method already matches the required functional interface.
List<String> names =
List.of(
"Amit",
"Neha",
"Rahul"
);
names.forEach(
System.out::println
);
The expression System.out::println is a method
reference.
Lambda with Collections
List<String> names =
new ArrayList<>();
names.add("Amit");
names.add("Neha");
names.add("Rahul");
names.forEach(
name ->
System.out.println(name)
);
Lambda for Sorting
List<Integer> numbers =
new ArrayList<>(
List.of(50, 10, 40, 20)
);
numbers.sort(
(a, b) -> Integer.compare(a, b)
);
System.out.println(numbers);
For simple natural ordering, Java also provides
Comparator.naturalOrder().
Lambda Expressions with Streams
Lambda expressions are widely used with the Stream API.
List<Integer> numbers =
List.of(
10, 15, 20, 25, 30
);
numbers.stream()
.filter(number -> number > 20)
.forEach(
number ->
System.out.println(number)
);
Variable Capture
A lambda can access local variables from its enclosing scope when those variables are final or effectively final.
int multiplier = 10;
Function<Integer, Integer> multiply =
number -> number * multiplier;
System.out.println(
multiply.apply(5)
);
multiplier if it is
being captured by the lambda.
this in Lambda Expressions
A lambda expression does not create a new this
context. Inside a lambda, this refers to the
enclosing object.
class Printer {
private String prefix = "Message: ";
void print() {
Consumer<String> action =
text ->
System.out.println(
prefix + text
);
action.accept("Hello");
}
}
Lambda vs Anonymous Class
Runnable task =
new Runnable() {
@Override
public void run() {
System.out.println(
"Running"
);
}
};
Runnable task =
() ->
System.out.println(
"Running"
);
The lambda form is more concise when the target type is a functional interface.
When Should You Use Lambda Expressions?
- Processing collections.
- Filtering and transforming stream data.
- Passing small pieces of behavior to APIs.
- Implementing functional interfaces.
- Creating concise callbacks and event-like operations.
Lambda Best Practices
- Keep lambda expressions short and readable.
- Use meaningful parameter names.
- Prefer method references when they improve readability.
- Avoid deeply nested lambda expressions.
- Use a regular method when the lambda becomes too complex.
Interview Questions
System.out::println for referring to
an existing compatible method.
Summary
Lambda expressions provide concise implementations of functional interfaces. They are heavily used with collections, Comparator, the Stream API, and the java.util.function package. Method references provide another concise way to reuse existing methods.