Java Stream Tutorial

Ashutosh Kumar Singh
5 min readApr 6, 2024

--

What is Stream?

  • Stream is conceptually a pipeline in which sequence of objects from a source undergoes processing either sequentially or parallely.
  • Processing include various operations like filtration, aggregation, reduction, sorting, flattening, mapping and many more.
  • It was introduced in Java 8 (java.util.stream) to express complex sequential operations in functional and declarative style which improve code quality and reduce verbosity.

Components of Stream

There are 3 major components required for stream.

  1. Data Source to create stream
  2. Intermediate operations to perform on data.
  3. Terminal operation to aggregate output.

Let’s talk about each of them in details.

Stream Creation:

There are multiple ways to create a stream. Let’s explore some of them.

  1. Empty Stream: We can create empty sequential stream by using Stream.empty() method. It is generally used where a method is expected to return a stream and there are no elements to be returned, Stream.empty() can be used as a default value instead of null.
  2. Stream.of(): It takes variable length arguments or direct array as input.

3. Stream from Array:

4. Stream from Collection:

5. Stream from Builder:

6. Stream from Iterate:

7. Stream from Generate:

8. Stream from Primitives:

9. Stream from chars:

10. Stream from tokens:

Intermediate Operations:

  • It return a stream after performing the operation.
  • There can be zero or more intermediate operations.
  • It doesn’t hamper original collection.
  • Let’s explore some of intermediate operations.
  1. filter(Predicate<? super T> predicate):
  • Predicate is a Functional Interface which takes one input and return boolean.
  • Using this boolean value, filter decide to keep the object in downstream. If return type is “true” then it will be included in result for next step or else discarded.

2. map(Function<? super T, ? extends R> mapper):

  • Function is a Functional Interface which takes T as input and return R as Output.

3. flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):

  • Generally used to flat nested lists.

4. distinct():

  • Remove duplicates from the stream.

5. sorted():

  • Sorts the stream.

6. peek(Consumer<? super T> action):

  • Consumer takes only one input as action.
  • It is mainly used for debugging the current state of element in pipeline.

7. limit(long maxSize):

  • limit the output to only maxSize.

8. skip(long n):

  • It skips first n elements in the encountered ordered of stream.

Terminal Operations:

  • Terminal operations invoke stream chains.
  • It return the result of required type after processing is completed.
  • Let’s explore some of terminal operations.
  1. forEach(Consumer<? super T> action):
  • Perform action for each element of the stream.
  • Consumer is a Functional Interface which takes one input and return void.

2. toArray():

  • Collect the elements of stream to array.
  • By default Object[] is returned.

3. reduce():

  • Reduces the stream into a single value.
  • There are three variations of this operation. Identity, Accumulator and Combiner.
  • Combiner works with parallel stream.

4. collect(Collector<? super T, A, R> collector):

  • It collect the elements of stream to given Collector type.
  • Collector type can be list, set, map or we can define our own collectors.

There are many other Terminal Operations such as min(), max(), count().

Operations such as anyMatch(), allMatch(), noneMatch(), findFirst(), findLast(), findAny() are known as Short-Circuit operations.

Lazy Evaluation

Stream are lazy in nature, it means when we create chains of stream it will not run until it got invoked by any Terminal Operation.

close()

  • A created stream can be utilized (i.e. triggered by Terminal Operation) only once.
  • BaseStream extends AutoCloseable interface which have close() method.
  • It’s a good practice to close IO streams using stream.close(). If there is a chance that it got unconsumed, then it will lead to resource leak.

Order of Execution

  • Stream go depth for an element until it require the result ready to perform stateful operation.
  • Stateful operations are those which requires all the elements from previous chain to be available like .sorted().
  • Let’s say we have following chain.
  • filter -> map -> sorted
  • Each element will go through filter -> map and then output will be gathered for next stateful operation.

Infinite Stream

  • It’s crucial to use limit() correctly before Terminal Operations, otherwise stream will run infinitely.

Parallel Stream

  • As the name suggest, it utilizes parallel processing capabilities.
  • Since it is parallel order is not guaranteed.
  • To create parallel stream, we just need to use .parallelStream() instead of .stream().
  • If the stream is other than of Collection or Array, we can use .parallel().

Conclusion

I hope you really enjoyed the article about Stream. If you want to explore about Functional Interface and Lambda Expression checkout my article Practical guide to Functional Interface and Lambda Expression in Java. Happy Learning and Happy Coding! 😊

--

--

Ashutosh Kumar Singh
Ashutosh Kumar Singh

Written by Ashutosh Kumar Singh

0 Followers

Full Stack Developer

No responses yet