Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.

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
22

Numeric 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.666666666666667

Item::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:

120

For 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 failed

A 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();.

Thanks for reading!

Donate to the site — we work hard for you.