Streams in Java
When Java 8 came into the picture, a significant new functionality came into existence, that is, Java Streams. You should not confuse Java Streams with the Java I/O Streams, as these have little to do with each other. Streams are used to process a collection of objects. Think of these streams as wrappers around a data source. In this article, we will discuss Streams in detail with explained example codes. So, let`s begin.
Why Streams?
Let us take an example to understand why and where streams are used in the first place. Say, you want to add all the numbers of a list which is greater than 10. The traditional method to do that would be the following.
Output
Let us look at the output of the above code.
Great! We got the answer as 27. There are two numbers in the list higher than 10, which are
11 and 16, and the sum of those numbers adds up to 27.
Now, let`s see how we can write the above code in a single line using Java Streams.
Output
Let us look at the output of the above code.
See! We got the same output. We can use Java Stream API to implement internal iteration. It helps in a bunch of features such as sequential and parallel execution, filtering, mapping, etc.
Creating a Stream
Streams are created from different element sources such as collection or array with the help of stream() and of() methods. Let us see how to create a stream.
Java Streams Operations and Pipelines
Java Streams operations are divided into two categories.
- Intermediate operations
- Terminal operations.
These operations are combined to form pipelines. A stream pipeline consists of a
source such
as Collection, Array or an I/O channel. These sources are followed by zero or more
intermediate operations such as Stream.filter, Stream.map,
Stream.sorted and a terminal
operation such as Stream.forEach or Stream.reduce.
Intermediate operations return a new stream. By executing an intermediate operation such
as filter(), it does not filter the given stream, but instead creates a new stream
that,
when traversed, contains the elements of the initial stream that match the given order.
Terminal operations, such as Stream.forEach traverses the stream to produce a
result.
After performing the terminal operation, the stream pipeline is considered exhausted and can
no longer be employed. If you need to traverse the same data source again, you must return
to the data source to get a new stream.
Enough of all the theory, let us jump to some example code to understand the operations
and pipelines of Java Streams.
Output
Let us first look at the output of the above example code.
Let me break down the above code for you and explain it bit by bit. After creating a list of
integers, which is, [2, 3, 4, 5]. For any operation of stream, the common format to
do that
is,
listName.stream().intermediateOperationRequired(variable ->
operationOnVariable).collect(Collectors.toList());
Instead of collect.(Collectors.toList()); we can use other terminal operations
as per
our requirement.
In the Map method, we are finding the cube of the given list of integers. In the next
part, we are creating a list of strings. In the filter method, using the startsWith
method
we filter the string starting with the letter ‘S.’ In the sorted method, we are arranging
the given list in the alphabetical order.
Summary
Java Streams is a sequence of objects which supports several methods. It can be pipelined to
produce the desired result.
Let us summarize the Stream operations we have seen in this article.
- Map: It produces a new stream from the original stream after applying an operation to each element of the original stream.
- Filter: It produces a new stream from the original stream after passing the given condition.
- Sorted: It produces a new stream from the original stream after sorting the stream.
- Collect: To get the final stream once all operations are performed.