The Stream API and The Lambda Expressions are two of the most important features that have been added to the Java language in a while (2014).
The Stream API provides methods that can perform very sophisticated operations like
- search
- filter
- map
or otherwise manipulate a set of data (or a large/very large set of data) easily.
The Stream API can also perform tasks "in parallel" thus making the operations more efficient than the traditional approaches that we have learnt until now.
CAUTION!!!
To properly understand the Stream API, you need to be able to
wrap your head around complicated topics like
- Generics and Type Erasure in Java
- Lambda Expressions and Functional Interfaces in Java
- Parallel Execution of programs
- The Collections Framework in Java
The Stream API defines quite some Interfaces/Classes/Abstract Classes in the Java JDK
which can be observed in the java.util.stream
package.
Just navigate to the java.base
module, and the follow the java.util.stream
package.
The java.util.stream.BaseStream
interface defines the functionality available in all streams.
It is a generic interface which is declared something like this:
interface BaseStream<T, S extends BaseStream<T, S>> {
// ...
}
The very first thing to learn about the Stream API is that is consists of three basic things
- A "Data Source" from where the data flows in.
- One or more intermediate operations. An intermediate operation produces a separate stream to perfom the operation, it can also be used to create a "pipeline" to perform a number of operations on the stream.
- An optional terminal operation. A terminal operation "consumes" the stream i.e once a stream is "consumed", it can't be reused. More about that later...
First let us see how we can create a simple Stream in a Java program.
To Create a "Stream" in Java, we can use the 'java.util.stream.Stream' interface. Since this interface is a Generic Type, we can actually use it to create a stream of any "reference" type in Java (pre-defined or programmer-defined).
To handle the Stream(s) of primitive type in Java, we have the following interfaces
- IntStream
- DoubleStream
- and LongStream
all defined in the same 'java.util.stream' package.
Let us see an example, we will use the IntStream
interface's range()
to create
a range of integers in a stream.
import java.util.stream.IntStream;
class Scratch {
public static void main(String[] args) {
var range = IntStream.range(1, 10);
// In the range() method, the first argument is included in the range
// but the second number is excluded.
}
}
Similarily we can create ranges with the LongStream
interface and the DoubleStream
interface as well.
Let us see how we can print all the numbers in this range now.
See here.
It does print the numbers in the range, but the output is a little wacky right?
No spaces between the numbers makes it look like just one number with a lot of digits!
Let's fix that!
here
Woah nice! Lambda Expressions!
Since the Stream API was built while keeping Lambda Expressions in mind
it is able to work with Lambdas freely!
As we have seen, the Stream API is able to benefit from the great deal of features available in the JDK, we can also use streams to work with collections of some data with the Collections Framework.
So, we can use a collection in our program to create a stream.
Also, we can use an array to create a stream by using the Arrays.stream()
method.
Let us see an example where we can create a stream with a collection as the data source. StreamCollectionExample
Now, let us perform some operations on the stream! SimpleStreamOperations