Previous in the Series
Current Tutorial
Adding a Terminal Operation on a Stream
Next in the Series

Previous in the Series: Reducing a Stream

Next in the Series: Finding the Characteristics of a Stream

# Adding a Terminal Operation on a Stream

## Avoiding the Use of the Reduce Method

A stream does not process any data if it does not end with a terminal operation. We already covered the terminal operation `reduce()`, and you saw several terminal operations in other examples. Let us now present the other terminal operations you can use on a stream.

Using the `reduce()` method is not the easiest way to reduce a stream. You need to make sure that the binary operator you provide is associative, then you need to know if it has an identity element. You need to check many points to make sure your code is correct and produces the results you expect. If you can avoid using the `reduce()` method, then you definitely should, because it's very easy to make mistakes with it.

Fortunately, the Stream API offers you many other ways to reduce streams: the `sum()`, `min()`, and `max()` that we covered when we presented the specialized streams of numbers are convenient methods that you can use instead of the equivalent `reduce()` calls. We are going to cover more methods in this part, which you should know, to avoid using the `reduce()` method. In fact, you should use this `reduce()` method as a last resort, only if you have no other solution.

## Counting the Elements Processed by a Stream

The `count()` method is present in all the stream interfaces: both in specialized streams and streams of objects. It just returns the number of elements processed by that stream, in a `long`. This number can be huge, in fact greater than `Integer.MAX_VALUE`, because it is a `long`.

You may be wondering why you would need such a great number. In fact, you can create streams with many sources, including sources that can produce huge amounts of elements, greater than `Integer.MAX_VALUE`. Even if it is not the case, it is easy to create an intermediate operation that will multiply the number of elements your stream processes. The `flatMap()` method, which we covered earlier in this tutorial can do that. There are many ways where you may end up with more elements to process than `Integer.MAX_VALUE`. This is the reason why the Stream API supports it.

Here is an example of the `count()` method in action.

``````Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten");

long count =
strings.stream()
.filter(s -> s.length() == 3)
.count();
System.out.println("count = " + count);
``````

Running this code produces the following result.

``````count = 4
``````

## Consuming Each Element One by One

The `forEach()` method of the Stream API allows you to pass each element of your stream to an instance of the `Consumer` interface. This method is very handy for printing the elements processed by a stream. This is what the following code does.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.forEach(System.out::println);
``````

Running this code prints out the following.

``````ONE
TWO
``````

This method is so simple that you may be tempted to use it in wrong use cases.

Remember that the lambda expressions you write should avoid mutating their outside scope. Sometimes, mutating outside the state is called conducting side-effects. The case of the consumer is special because a consumer that does not have any side effect will not do much for you. In fact, calling `System.out.println()` creates a side effect on the console of your application.

Let us consider the following example.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");
List<String> result = new ArrayList<>();

strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)

System.out.println("result = " + result);
``````

Running the previous code prints out the following.

``````result = [ONE, TWO]
``````

So you may be tempted to use this code because it's simple, and it "just works". Well, there are several wrong things that this code is doing. Let us go through them.

Calling `result::add` adds all the elements processed by that stream to the outside `result` list by mutating that list from within the stream. This consumer is creating a side effect to a variable outside the scope of the stream itself.

Accessing such a variable makes your lambda expression a capturing lambda expression. It is perfectly legal to create such lambda expressions; you just need to be aware that there is an important performance hit in doing so. If performance is an important matter in your application, then you should avoid writing capturing lambdas.

Moreover, this way of writing things prevents you from making this stream parallel. Moreover, this way of consuming elements is problematic if you try to make this stream parallel. If you do, then you will have several threads accessing your result list concurrently. This list is an instance of `ArrayList`, not a class tailored to handle concurrent access.

You have two patterns to store the elements of a stream in a list. The following example demonstrates the first pattern, which uses a collection object. The second pattern, which uses Collector objects, is covered later.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
``````

This collector creates an instance of `ArrayList` and adds the elements processed by your stream in it. So this pattern is not creating any side effect so there is no performance hit.

Parallelism and concurrency are handled by the Collector API itself, so you can safely make this stream parallel.

This pattern code is as simple and readable as the previous one. It does not have any of the drawbacks of creating side effects within a consumer object. This is definetely the pattern you should be using in your code.

Starting with Java SE 16, you have a second, even simpler, pattern.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.toList();
``````

This pattern produces a special instance of `List` that is immutable. If what you need is a mutable list, you should stick to the first collector pattern. It also performs better than collecting your stream in an instance of `ArrayList`. This point is covered in the next paragraph.

## Collecting Stream Elements in a Collection, or an Array

The Stream API offers you several ways of collecting all the elements processed by a stream into a collection. You had a first glimpse at two of those patterns in the previous section. Let us see the others.

There are several questions you need to ask yourself before choosing which pattern you need.

• Do you need to build an immutable list?
• Are you comfortable with an instance of `ArrayList`? Or would you prefer an instance of `LinkedList`?
• Do you have a precise idea of how many elements your stream is going to process?
• Do you need to collect your element in a precise, maybe third party or homemade implementation of `List`?

The Stream API can handle all these situations.

### Collecting in a Plain ArrayList

You already used this pattern in a previous example. It is the simplest you can use and returns the elements in an instance of `ArrayList`.

Here is an example of such a pattern in action.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
``````

This pattern creates a simple instance of `ArrayList` and accumulates the elements of your stream in it. If there are too many elements for the internal array of the `ArrayList` to store them, then the current array will be copied into a larger one and will be handled by the garbage collector.

If you want to avoid that, and you know the amount of elements your stream will produce, then you can use the `Collectors.toCollection()` collector, which takes a supplier as an argument to create the collection in which you will be collecting the processed elements. The following code uses this pattern to create an instance of `ArrayList` with an initial capacity of 10,000.

``````Stream<String> strings = ...;

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toCollection(() -> new ArrayList<>(10_000)));
``````

### Collecting in an Immutable List

There are cases where you need to accumulate your elements in an immutable list. This may sound paradoxical because collecting consists in adding elements to a container that has to be mutable. Indeed, this is how the Collector API works as you will see in more details later in this tutorial. At the end of this accumulating operation, the Collector API can proceed with a last, optional, operation, which, in this case, consists in sealing the list before returning it.

To do that, you just need to use the following pattern.

``````Stream<String> strings = ...;

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableList()));
``````

In this example, `result` is an immutable list.

Starting with Java SE 16, there is a better way to collect your data in an immutable list, which can be more efficient on some cases. The pattern is the following.

``````Stream<String> strings = ...;

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.toList();
``````

How can it be more efficient? The first pattern, built on the use of a collector, begins by collecting your elements in a plain `ArrayList` and then seals it to make it immutable when the processing is done. What your code sees is just the immutable list built from this `ArrayList`.

As you know, an instance of `ArrayList` is built on an internal array that has a fixed size. This array can become full. In that case, the `ArrayList` implementation detects it and copies it into a larger array. This mechanism is transparent for the client, but it comes with an overhead: copying this array takes some time.

There are cases where the Stream API can keep track of how many elements are to be processed before all the stream is consumed. In that case, creating an internal array of the right size is more efficient because it avoids the overhead of copying small arrays into larger ones.

This optimization has been implemented in the `Stream.toList()` method, which has been added to Java SE 16. If what you need is an immutable list, then you should be using this pattern.

### Collecting in a Homemade List

If you need to collect your data in your own list or third party list outside the JDK, then you can use the `Collectors.toCollection()` pattern. The supplier you used to tune the initial size of you instance of `ArrayList` can also be used to build any implementation of `Collection`, including implementations that are not part of the JDK. All you need to give is a supplier. In the following example, we provide a supplier to create an instance of `LinkedList`.

``````Stream<String> strings = ...;

List<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
``````

### Collecting in a Set

Because the `Set` interface is an extension of the `Collection` interface, you could use the pattern `Collectors.toCollection(HashSet::new)` to collect your data in an instance of `Set`. This is fine, but the Collector API still gives you a cleaner pattern to do that: the `Collectors.toSet()`.

``````Stream<String> strings = ...;

Set<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toSet());
``````

You may be wondering if there is any difference between these two patterns. The answer is yes, there is a subtle difference, which you will see later in this tutorial.

If what you need is an immutable set, the Collector API has another pattern for you: `Collectors.toUnmodifiableSet()`.

``````Stream<String> strings = ...;

Set<String> result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableSet());
``````

### Collecting in a Array

The Stream API also has its own set of `toArray()` method overloads. There are two of them.

The first one is a plain `toArray()` method, that returns an instance of `Object[]`. If the exact type of your stream is known, then this type is lost if you use this pattern.

The second one takes an argument of type `IntFunction<A[]>`. This type may look scary at first, but writing an implementation of this function is in fact very easy. If you need to build an array of strings of characters, then the implementation of this function is `String[]::new`.

``````Stream<String> strings = ...;

String[] result =
strings.filter(s -> s.length() == 3)
.map(String::toUpperCase)
.toArray(String[]::new);

System.out.println("result = " + Arrays.toString(result));
``````

Running this code produces the following result.

``````result = [ONE, TWO]
``````

## Extracting the Maximum and the Minimum of a Stream

The Stream API gives you several methods for that, depending on what stream you are currently working with.

We already covered the `max()` and `min()` methods from the specialized streams of numbers: `IntStream`, `LongStream` and `DoubleStream`. You know that these operations do not have an identity element, so you should not be surprised to discover that there are all returning optional objects.

By the way, the `average()` method from the same streams of number also returns an optional object, since the average operation does not have an identity element neither.

The `Stream` interface also has the two methods `max()` and `min()`, that also return an optional object. The difference with the stream of objects is that the elements of a `Stream` can really be of any kind. To be able to compute a maximum or a minimum, the implementation needs to compare these objects. This is the reason why you need to provide a comparator for these methods.

Here is the `max()` method in action.

``````Stream<String> strings = Stream.of("one", "two", "three", "four");
String longest =
strings.max(Comparator.comparing(String::length))
.orElseThrow();
System.out.println("longest = " + longest);
``````

It will print the following on your console.

``````longest = three
``````

Remember that trying to open an optional object that is empty throws a `NoSuchElementException`, which is something you do not want to see in your application. It happens only if your stream does not have any data to process. In this simple example, you have a stream that processes several strings of character with no filter operation. This stream cannot be empty, so you can safely open this optional object.

## Finding an Element in a Stream

The Stream API gives you two terminal operations to find an element: `findFirst()` and `findAny()`. These two methods do not take any argument and return a single element of your stream. To properly handle the case of empty streams, this element is wrapped in an optional object. If your stream is empty, then this optional is also empty.

Understanding which element is returned requires you to understand that streams may be ordered. An ordered stream is simply a stream in which the order of the elements matters and is kept by the Stream API. By default, a stream created on any ordered source (for instance an implementation of the `List` interface) is itself ordered.

On such a stream, it makes sense to have a first, second, or third element. Finding the first element of such a stream then makes perfect sense too.

If your stream is not ordered, or if the order has been lost in your stream processing, then finding the first element is undefined, and calling `findFirst()` returns in fact any element of the stream. You will see more details on ordered streams later in this tutorial.

Note that calling `findFirst()` triggers some checking in the stream implementation to make sure that you get the first element of that stream if that stream is ordered. This can be costly if your stream is a parallel stream. There are many cases in which getting the first found element is not relevant, including cases where your stream only processes a single element. In all these cases, you should be using `findAny()` instead of `findFirst()`.

Let us see `findFirst()` in action.

``````Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten");

String first =
strings.stream()
// .unordered()
// .parallel()
.filter(s -> s.length() == 3)
.findFirst()
.orElseThrow();

System.out.println("first = " + first);
``````

This stream is created on an instance of `List`, which makes it an ordered stream. Note that the two lines `unordered()` and `parallel()` are commented in this first version.

Running this code several times will always give you the same result.

``````first = one
``````

The `unordered()` intermediate method call makes your ordered stream an unordered stream. In this case it does not make any difference because your stream is processed sequentially. Your data is pulled from a list that always traverses its elements in the same order. Replacing the `findFirst()` method call with a `findAny()` method call does not make any difference either for the same reason.

The first modification that you can make on this code is to uncomment the `parallel()` method call. Now you have an ordered stream, processed in parallel. Running this code several times will always give you the same result: `one`. This is because your stream is ordered, so the first element is defined, no matter how your stream has been processed.

To make this stream unordered, you can either uncomment the `unordered()` method call or replace the `List.of()` with a `Set.of()`. In both cases, terminating your stream with `findFirst()` will return a random element from that parallel stream. The way parallel streams are processed makes it so.

The second modification that you can make in this code, is to replace `List.of()` by `Set.of()`. Now this source is not ordered anymore. Moreover, the implementation returned by `Set.of()` is such that the traversing of the elements of the set happens in a randomized order. Running this code several times shows you that both `findFirst()` and `findAny()` return a random string of characters, even if `unordered()` and `parallel()` are both commented out. Finding the first element of nonordered source is not defined, and the result is random.

From these examples, you can deduce that there are some precautions taken in the implementation of the parallel stream to track which element is the first. This constitutes an overhead, so in this case, you should only call `findFirst()` if you really need it.

## Checking if the Elements of a Stream Match a Predicate

There are cases where finding an element in a stream or failing to find any element in a stream may be what you really need to do. The element you find is not relevant for your application; what it is important is that this element exists.

The following code would work to check for the existence of a given element.

``````boolean exists =
strings.stream()
.filter(s -> s.length() == 3)
.findFirst()
.isPresent();
``````

In fact, this code checks if the returned optional is empty or not.

The previous pattern works fine, but the Stream API gives you a more efficient way to do it. In fact, building this optional object is an overhead, which you do not pay if you use one of the three following methods. These three methods take a predicate as an argument.

Let us see these methods in action.

``````Collection<String> strings =
List.of("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten");

boolean noBlank  =
strings.stream()
.allMatch(Predicate.not(String::isBlank));
boolean oneGT3   =
strings.stream()
.anyMatch(s -> s.length() == 3);
boolean allLT10  =
strings.stream()
.noneMatch(s -> s.length() > 10);

System.out.println("noBlank = " + noBlank);
System.out.println("oneGT3  = " + oneGT3);
System.out.println("allLT10 = " + allLT10);
``````

Running this code produces the following result.

``````noBlank = true
oneGT3  = true
allLT10 = true
``````

## Short-Circuiting the Processing of a Stream

You may have noticed an important difference between the different terminal operation that we have covered here.

Some of them require the processing of all the data consumed by your stream. This is the case of the COUNT, MAX, MIN, AVERAGE operations, as well as the `forEach()`, `toList()`, or `toArray()` method calls.

It is not the case for the last terminal operations we covered. The `findFirst()` or `findAny()` methods will stop processing your data as soon as an element is found, no matter how many elements are left to be processed. The same goes for `anyMatch()`, `allMatch()`, and `noneMatch()`: they may interrupt the processing of the stream with a result without having to consume all the elements your source can produce.

There are still cases where these last methods need to process all the elements:

These methods are called short-circuiting methods in the Stream API because they can produce a result without having to process all the elements of your stream.

### In this tutorial

Last update: September 14, 2021

Previous in the Series
Current Tutorial
Adding a Terminal Operation on a Stream
Next in the Series

Previous in the Series: Reducing a Stream

Next in the Series: Finding the Characteristics of a Stream