Hero Without a Cape: Stream API
Hero Without a Cape: Stream API
Let's learn more about this Java hero!
Join the DZone community and get the full member experience.Join For Free
In this post, we will be talking about the Stream API added to Java 8 and how it has changed the way we do programs in Java. It added neatness as well as made the code more readable. It helps conduct functional programming in Java. So without any further ado, let's learn more about this Java gem!
You may also like: Your Guide to Java Streams [Tutorials & Articles]
What Is the Stream API?
The Stream API, included in Java 8, is utilized for processing
collections of Objects. It is the flow of Objets on which various methods get applied in the pipeline to have the result. Simply put, it flows through the given
Collection<Object> and applies the different methods on the
Object to aggregate them into the desired result without affecting the original
What Stream Provides
Stream doesn’t store anything; instead, it operates on the given
Array, or I/O (yes, you can use it on I/O). Without changing the original data, it applies the methods to the data. Lazy evaluation helps to add multiple
intermediate operations without running the full stream. Evaluated only after we add
terminal operation. it adds Neatness and make codes cleaner. Produce a more comprehensive code. And many more.
Things to Know Before You Go
Stream allows you to practice functional programming in Java. This means that you should be comfortable with functional interfaces and lambda expressions. If you understand these concepts well, no one can stop you from understanding the Stream API. You can find code examples on GitHub or view the full project here.
How to Stream
Streams can be implemented in multiple ways. A few magic tricks are:
- Stream’s static factory methods such as
IntStream.range(int, int), or
- Streaming the line of a file by
- Streams of random numbers can be obtained from
The first and second bullets are what we use most often. For our discussion, we will be sticking to the
Collection#steam() method. For example:
Stream Pipeline Operations
We can perform two operations on a Stream pipeline:
- Intermediate Operation
- Terminal Operation
Intermediate operation transforms or filters the object in the preceding pipeline. These methods are nothing but the
Functional Interfaces we mentioned previously. You should get familiar with them to easily understand this operation without any trouble.
Stream won’t execute intermediate operations unless the terminal operation is called. It is an indication for the Stream to start the pipeline. Once we call the terminal operation, the pipeline ends and it returns the desired result. After the terminal operation, we can't use intermediate operations since the pipeline has ended. It can only be called once at the end of the pipeline. However, you can again start the stream if the terminal operation returns a collection — which I highly oppose doing. You can pretty much do all the things inside a single stream pipeline only.
In the Stream pipeline, we can have multiple intermediate operations that transform or filter the data which will be flowing towards the next pipe without affecting the actual data on the Collection. These operations return the Stream of the desired result instead of the actual result through, which we can add more intermediate operation without breaking the Stream pipeline. We will be talking about the
filter in this post and others in another article. Methods
filter are the ones you will be needing most of the time.
These methods are applied one by one to the elements in the pipeline and not all at once.
map(Function) as a transforming function that transforms the given object to another. It accepts a
Function as a parameter, which takes the element in the stream as an input, performs some operation we tell it to, and returns the Stream of the resulting object. If we look at our previous example, it is multiplying each number by 2. We can have another map that can add 2 to the number and then another one to convert it to another object, and then another one, and so on — you get the idea.
In the above example, look at how we transformed a list of
Integer to the
String. Let's break it down:
- Multiplying each number by 2. So, 1 becomes 2, 2 becomes 4, and so on.
- We have attached the number to a string resulting in a String Object.
- The previous map made the
Stream<String>due to the return type of string.
- We again attached another string to perform some operation.
- Finally printing the results, which looks like:
The filter, as the name suggests, filters out the elements before it reaches the next pipe. Simply put, if the element satisfies the condition, it allows it to pass through; otherwise, it won’t allow for the next operation. It takes a
Predicate as a parameter.
So many filters. Ok, let's break this down, shall we?
- Take only even numbers ahead.
- Forward only numbers greater than 5
- Multiply the number by 5. Again, just to show you can have multiple intermediate operations in a single Stream pipeline.
- Allow numbers that are less than 50.
count(Terminal Operation) how many survived after this much filtering.
Until now, we have seen operations perform something in the pipeline and return the stream. In the end, we will be needing a final result. That’s where terminal operation comes in. It not only gives us the desired result, but it also starts the Stream. As I said, Stream is lazy; it won’t do anything, no matter how many intermediate operations we add to the pipeline, unless it sees its end, the terminal operation.
You cant use the same stream after it was closed by a terminal operation. Otherwise, it will throw
IllegalStateException: stream has already been operated upon or closed. You have to start a new stream
The terminal operations we have used in the above examples are
count. Let us examine them and a few others below.
forEach(Consumer) takes the input but returns no output, taking
Consumer as a parameter. Use this if you want to do something without returning anything. We have used it for printing the numbers.
As the name suggests, it is used for counting the elements reached to the end. Like in our filter example, counting who satisfied all the filter’s conditions.
I use this more often than any other terminal operation. It allows you to create the
Collection from the elements of the stream after processing them. These
Collection can be
Map. The Collection includes elements that passed through the last pipe.
In the pipeline, we have multiplied the number by 5, then filtered numbers less than 30 and added 2. And after all the elements are done processing, we collect them into the list.
We have studied what the Stream API is, how to obtain it, and how to best use it. This post is kind of like an introductory post for the Stream API. There are so many diamonds in the mine, which we will be mining in our next post.
Published at DZone with permission of Imran Shaikh . See the original article here.
Opinions expressed by DZone contributors are their own.