Going Deeper (Modern & Professional Java)Pro· 45 min read

Lambdas & the Streams API

Lambdas are tiny inline functions; the Streams API uses them to filter, transform and summarise collections in clean, readable steps.

What you will learn

  • Write a lambda expression
  • Filter and transform a list with a stream
  • Collect results back into a list or a single value

What is a lambda?

A lambda expression is a very short, nameless function you can write right where you need it. Instead of declaring a whole method, you write just the inputs and what to do with them, joined by an arrow ->.

The shape is: (inputs) -> result. For example, x -> x * 2 is a lambda that takes x and gives back x * 2. You met one already in the Comparator lesson: s -> s.name. Lambdas shine when a method needs a small piece of behaviour passed to it.

A lambda always plugs into a functional interface — an interface with exactly one method. The lambda becomes that method body. Java has many built-in ones; you rarely write your own.

A lambda stored as a Function and called with apply
import java.util.function.Function;

public class Main {
    public static void main(String[] args) {
        // a lambda stored in a Function: takes an Integer, returns an Integer
        Function<Integer, Integer> doubler = x -> x * 2;

        System.out.println(doubler.apply(5));
        System.out.println(doubler.apply(20));
    }
}

Note: Output: 10 40 x -> x * 2 is the whole function: take x, return x times 2. We stored it in a Function and ran it with apply. No class, no method declaration — just the logic. That compactness is what makes lambdas so useful for the next part.

Streams: a pipeline over a collection

The Streams API lets you process a collection as a series of clear steps, like an assembly line. You start a stream from a list, then chain operations: filter (keep only some), map (transform each), and finally collect or reduce into a result. Each step reads like a sentence.

Crucially, a stream does not change the original list — it produces a new result, leaving your data untouched.

Filtering a list with a stream pipeline
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(4, 7, 10, 15, 20, 23);

        List<Integer> bigEvens = numbers.stream()
            .filter(n -> n % 2 == 0)      // keep only even numbers
            .filter(n -> n > 5)           // and only those above 5
            .collect(Collectors.toList());

        System.out.println(bigEvens);
    }
}

Note: Output: [10, 20] The stream went number by number. The first filter kept the even ones (4, 10, 20), the second kept those above 5 (10, 20), and collect gathered the survivors into a new list. The original numbers list is unchanged. Read top to bottom, the pipeline says exactly what it does.

map: transform every item

map takes each item and turns it into something else, using a lambda. Here we take a list of names and produce a new list of their lengths.

map transforms each element into a new value
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Asha", "Ravi", "Mia");

        List<String> shouted = names.stream()
            .map(name -> name.toUpperCase())   // transform each name
            .collect(Collectors.toList());

        System.out.println(shouted);
    }
}

Note: Output: [ASHA, RAVI, MIA] map(name -> name.toUpperCase()) ran the lambda on every name, building a new list of the uppercase versions. map is the go-to step whenever you want to convert each item in a collection into something else.

Reducing to a single answer

Streams can also boil a whole collection down to one value — a sum, a count, an average. These finishing steps are called terminal operations.

Filtering then summing and counting with a stream
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> prices = List.of(40, 75, 20, 95);

        int total = prices.stream()
            .filter(p -> p >= 50)         // only the pricier items
            .mapToInt(p -> p)             // treat them as plain ints
            .sum();                       // add them up

        long count = prices.stream().filter(p -> p >= 50).count();

        System.out.println("Total of pricey items: " + total);
        System.out.println("How many: " + count);
    }
}

Note: Output: Total of pricey items: 170 How many: 2 The stream kept prices of 50 or more (75 and 95), then sum() added them to 170 and count() found there were 2. One readable pipeline replaced a loop, an if, and a running total. That clarity is why streams are everywhere in modern Java.

The mental model

  1. Start a stream from a collection with .stream().
  2. Filter to keep only the items you want (filter).
  3. Transform each item if needed (map).
  4. Finish with a terminal step: collect into a new list, or sum / count / average into a single value.

Watch out: A stream is single-use. Once you call a terminal step (collect, sum, count), that stream is finished — you cannot reuse the same stream variable again. Just start a fresh .stream() from the list when you need another pass.

Tip: You can usually replace name -> name.toUpperCase() with a tidy method reference: String::toUpperCase. It means the same thing and reads even more cleanly once you are comfortable with lambdas.

Q. In a stream pipeline, what does filter(n -> n > 5) do?

Answer: filter keeps the elements where the lambda returns true and drops the rest, producing a new stream of just the survivors. It never changes the original collection.

✍️ Practice

  1. From a List.of("apple", "fig", "banana", "kiwi"), use a stream to keep only words longer than 4 letters and collect them into a list.
  2. From a list of numbers, use a stream to map each to its square and print the result.

🏠 Homework

  1. Given a list of integer ages, use streams to (a) count how many are 18 or older, and (b) build a new list of only the adult ages. Print both results.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →