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.
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.
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.
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.
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
- Start a stream from a collection with
.stream(). - Filter to keep only the items you want (
filter). - Transform each item if needed (
map). - Finish with a terminal step:
collectinto a new list, orsum/count/averageinto 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?
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
- 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. - From a list of numbers, use a stream to map each to its square and print the result.
🏠 Homework
- 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.