Stream operations
Streams have dozens of operations, but everyday code mostly uses about ten. In this lesson we group them: selecting, transforming, ordering and getting the result.
filter and map
filter keeps the elements for which the lambda returned true. map turns each element into something else — for example, a string into its length:
void main() {
List<String> words = List.of("cat", "dog", "hedgehog", "parrot");
List<Integer> lengths = words.stream()
.map(w -> w.length())
.toList();
IO.println(lengths);
}Output:
[3, 3, 8, 6]The element type can change after map: a stream of strings became a stream of numbers. The toList() method collects the result into an unmodifiable list.
Order and uniqueness
sorted() sorts the elements, distinct() removes duplicates, limit(n) keeps the first n elements and skip(n) skips them:
void main() {
List<Integer> scores = List.of(7, 3, 9, 3, 10, 7, 5);
List<Integer> top = scores.stream()
.distinct()
.sorted(Comparator.reverseOrder())
.limit(3)
.toList();
IO.println(top);
IO.println(scores.stream().skip(5).toList());
}Output:
[10, 9, 7]
[7, 5]Without arguments sorted sorts in ascending order; with a comparator it follows the comparator. Comparators are covered in detail in the collections chapter.
Checks and search
anyMatch, allMatch and noneMatch answer "is there any / are all / is there none". findFirst() returns the first element wrapped in an Optional, because there may be no matching element at all:
void main() {
List<Integer> ages = List.of(15, 22, 17, 30);
IO.println(ages.stream().anyMatch(a -> a >= 18));
IO.println(ages.stream().allMatch(a -> a >= 18));
Optional<Integer> firstAdult = ages.stream()
.filter(a -> a >= 18)
.findFirst();
IO.println(firstAdult.orElse(0));
}Output:
true
false
22Numeric streams
Numbers have special streams: IntStream, LongStream and DoubleStream. mapToInt switches to one, after which sum(), average() and max() are available:
record Item(String name, int price) {}
void main() {
List<Item> cart = List.of(new Item("Bread", 3), new Item("Cheese", 12), new Item("Tea", 5));
int total = cart.stream().mapToInt(Item::price).sum();
double avg = cart.stream().mapToInt(Item::price).average().orElse(0);
IO.println("Total: $" + total);
IO.println("Average price: $" + avg);
}Output:
Total: $20
Average price: $6.666666666666667Item::price is a method reference, a short form of the lambda i -> i.price(). average() returns an OptionalDouble: an empty stream has no average. Numeric streams and flatMap are covered in more detail in the flatMap and numeric streams lesson.
reduce
reduce folds all elements into a single value. You pass it a starting value and a function that combines the accumulated result with the next element:
void main() {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int product = numbers.stream().reduce(1, (acc, n) -> acc * n);
IO.println(product);
}Output:
120For sums, minimums and maximums the ready-made sum(), min() and max() are more convenient — reduce is for when there is no ready-made method.
Common mistake
You can't change an ordinary local variable inside a lambda — the compiler forbids it:
void main() {
int total = 0;
List.of(1, 2, 3).stream().forEach(n -> total += n);
IO.println(total);
}Output:
Main.java:3: error: local variables referenced from a lambda expression must be final or effectively final
List.of(1, 2, 3).stream().forEach(n -> total += n);
^
1 error
error: compilation failedA local variable used in a lambda must be "effectively final" — not changed after it is assigned. Instead of accumulating into an outer variable, return the result from the stream: int total = list.stream().mapToInt(n -> n).sum();.