Streams & Lambdas
Lambdas and the Stream API (introduced in Java 8) bring functional programming to Java. Lambda expressions let you write concise anonymous functions, and the Stream API provides a declarative way to process collections — filtering, transforming, and aggregating data with elegant, readable code.
Together, lambdas and streams replace verbose loops with a pipeline of operations that describe what you want to do with your data, rather than how to do it step by step.
What are lambdas?
- → Lambda — An anonymous function that can be passed as a value.
- → Syntax —
(parameters) -> expressionor(parameters) -> { statements; } - → Functional interface — An interface with a single abstract method that a lambda can implement.
- → Concise — Lambdas eliminate the boilerplate of anonymous inner classes.
Lambda expressions
Lambdas work with functional interfaces — interfaces with exactly one abstract method. Java provides many built-in functional interfaces like Predicate<T>, Function<T,R>, and Consumer<T>. You can also use @FunctionalInterface to define your own.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
// Lambda with forEach
names.forEach(name -> System.out.println(name));
// Lambda with Predicate (filtering)
names.stream()
.filter(name -> name.length() > 4)
.forEach(name -> System.out.println("Long name: " + name));
// Multiple lines in a lambda
names.forEach(name -> {
int len = name.length();
System.out.println(name + " has " + len + " characters");
});
}
}
Stream API basics
The Stream API lets you process collections with a chain of operations. You create a stream from a collection, apply intermediate operations (like filter and map), and finish with a terminal operation (like forEach or collect).
Try it
Modify the filter to keep only numbers greater than 5, or change the map operation to square each number (n * n).
Stream operations
Streams support two types of operations: intermediate (lazy, return a new stream) and terminal (trigger processing, produce a result). The pipeline only executes when a terminal operation is called.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
List<String> words = Arrays.asList("hello", "world", "java", "stream", "lambda");
// filter — keep elements matching a condition
// map — transform each element
// sorted — sort elements
// count — count elements
// collect — gather results into a collection
long result = words.stream()
.filter(w -> w.length() > 4)
.map(String::toUpperCase)
.sorted()
.count();
System.out.println("Words > 4 chars (uppercase, sorted): " + result);
List<String> filtered = words.stream()
.filter(w -> w.startsWith("s"))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Words starting with S: " + filtered);
Method references
When a lambda simply calls an existing method, you can use a method reference for even more concise code. There are four forms: static methods, instance methods, arbitrary object methods, and constructor references.
import java.util.Arrays;
import java.util.List;
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Lambda equivalent:
names.forEach(name -> System.out.println(name));
// Method reference (shorter):
names.forEach(System.out::println);
// Map with method reference
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
Key concepts
- → filter() — Keeps elements that match a predicate.
- → map() — Transforms each element into something new.
- → forEach() — Performs an action on each element.
- → collect() — Gathers stream results into a collection.
- → sorted() — Sorts elements in natural order.
- → count() — Returns the number of elements.
Quiz
Create a list of integers from 1 to 20. Use streams to: (1) filter only even numbers, (2) map each to its square, (3) collect the results into a list and print it.