Intermediate operations

Intermediate operations #

In a stream pipeline, an intermediate operation transforms a stream into another stream.

Stateless operations #

A stateless intermediate operation processes each element of the input stream independently, and without the need to memorize information about previous elements in the stream.

Stream.filter and Stream.map #

These two instance methods allow specifying a stateless operation, via a callback method. We have already explained their behavior in detail in our initial pipeline example.

Stream.flatMap #

This is another very useful instance method, which can produce several objects out a single object in the stream.

Example. Consider the following classes City and Region.

Given as input a collection regions of regions, we can use the function flatMap to create a stream that consists of all cities in these regions, as follows:

regions.stream()
    .flatMap(r -> r.cities.stream())

For instance, the following method takes as argument a list of regions, and returns the zip codes of all cities in these regions, with the exceptions of the zip code of Bologna:

List<Integer> allZipCodesButBologna(List<Region> regions) {
    return regions.stream()
        .flatMap(r -> r.cities.stream())
        .filter(c -> !c.name.equals("Bologna"))
        .map(c -> c.zipCode)
        .toList();
}

Let us assume that the stream has type Stream<$\mathit{T}$> (for instance, in our example, $\mathit{T}$ is Region).

The method flatMap takes as argument a callback function of type

$\qquad T \to$ Stream<$T’$>

where $T’$ can be any type.

In this example, the callback function is

r -> r.cities.stream()

which has type

$\qquad$ Region $\to$ Stream<City>

Let us name this callback function $f$.

The method flatMap returns a Stream<$\mathit{T’}$> (e.g. in this example a Stream<City>). This stream is the concatenation of all streams $f(a)$ such that $a$ belongs to the original stream.

Stateful operations #

Stateful intermediate operations are the ones that are not stateless.

We list here some of the most useful ones.

Stream.distinct #

This instance method produces an identical stream, but without duplicates.

Example.

List<Integer>list = List.of(2, 1, 1, 3, 1, 2);

// Contains [2, 1, 3]
List<Integer> ouputList = list.stream()
                            .distinct()
                            .toList();

For a stream of objects, duplicates are identified based on Java’s object equality (i.e. as specified by the method Object.equals).

Reminder. If a class overrides the method Object.equals, then it should also override Object.hashCode (and these two implementations should be consistent with each other).

Stream.sorted #

This instance method outputs a sorted stream. It can be called with or without a Comparator.

Its behavior (with and without a comparator) is analogous to the methods Arrays.sort and Collection.sort.

Example.

List<Integer>list = List.of(2, 1, 3, 1, 2);

// Contains [1, 1, 2, 2, 3]
List<Integer> ouputList = list.stream()
                            .sorted()
                            .toList();

Hint. Recall that Comparator<$T$> is a functional interface, whose (only) method is compare($T$ e1, $T$ e2). As a result, it can be implemented with an anonymous method.

For instance, the following method removes duplicates in a list of cities, and returns it sorted:

List<City> sortDistinctCitiesByName(List<City> cities){
    return cities.stream()
                .distinct()
                .sorted((c1, c2) -> c1.name.compareTo(c2.name))
                .toList();
}

Stream.limit #

This instance method takes as argument an integer $n$, and outputs the $n$ first elements of the stream.

Example.

List<Integer>list = List.of(2, 1, 3, 1, 2);

// Contains [2, 1, 3, 1]
List<Integer> ouputList = list.stream()
                            .limit(4)
                            .toList();

Stream.skip #

This instance method takes as argument an integer $n$, and outputs the stream without its $n$ first elements.

Example.

List<Integer>list = List.of(2, 1, 3, 1, 2);

// Contains [3, 1, 2]
List<Integer> ouputList = list.stream()
                            .skip(2)
                            .toList();