☕ Java

Streams & Lambdas

Lesson 5 of 5 ~5 min

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 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).

Editor
Java
Output

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

Quiz

Which Stream method transforms each element?
Challenge

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.