8 Years of Java Stream API, Understand Streams Through 8 Questions!

Manoj Chemate
Level Up Coding
Published in
5 min readSep 17, 2022

--

Yes, It has been 8 years since Java SE 8 was released with Stream APIs, Functional Interfaces, Time API, and many more important features but Lambdas and Streams are what introduced Java Developers to Functional Programming.

Let’s understand Streams and their usage!

Photo by Matt Paul Catalano on Unsplash

Some Basics:

  1. Functional Interface: Interface that has a single abstract method.
  2. Lambda Expression: Implements a single abstract method of functional Interface.
  3. Streams: Lambda expressions are passed as arguments to stream operations to read/modify data.

1. What is a Stream?

Provides a way to perform a chain of operations on a sequence of elements using the functional programming paradigm. If you don’t know functional programming, take look a quick look at the 8th question.

Collect squares of numbers greater than twenty-five.

2. What is NOT a Stream?

  1. Stream is not a data structure like Collections, Stream just knows where to look for elements when required in the pipeline.
  2. Collections focus on organizing data efficiently.
  3. Streams focus on organizing computations efficiently.

3. How to create a Stream?

Stream is composed of three parts

  1. Source of elements
  2. Zero or more intermediate operations
  3. A single terminal operation that triggers the stream.

1. Source

The Source of elements can be collections, arrays, generator functions, and I/O channels. You can create a sequential or parallel stream on the source using different methods. Listed some of them below.

Methods to create a Stream

Parallel streams use worker threads to perform operations parallelly on the source of elements. It is important to note that parallel streams are not always faster but it does have their own overhead of dividing the stream into multiple parts and combining results at the end. One has to note that in such cases, Lambdas have to be implemented in a stateless manner since they could be executed by multiple threads parallelly. Keeping these things aside, parallel streams do have a lot of potentials to optimize performance.

You will know when there is a need for parallel streams so keep it simple until then !

2. Intermediate Operations:

  1. A Stream can have zero or more intermediate operations.
  2. It can be a stateful or stateless operation. Stateful operations like sorted() depend on all the elements in the stream whereas stateless operations like filter(), and map() can be performed independently on a stream element. Stateless operations can be optimized internally by performing parallel executions.
Intermediate Operations

3. Terminal Operation:

  1. This operation triggers the stream pipeline to perform operations
  2. Operation can be short-circuiting like limit(), findFirst() which reduces length of the stream or non–short-circuiting like collect(), forEach().
Terminal Operations

5. How to Use a Stream?

Stream Examples

6. How Does Stream Work Internally?

Creating Stream:

  1. Spliteterator is another iterator introduced in Java SE 8 which supports parallel programming by splitting and iterating over stream elements.
  2. Stream also captures different state flags which describe characteristics of elements, if the source is TreeSet then flag SORTED is captured.

Flags like SIZED, DISTINCT, SORTED, and ORDER are used to capture state of stream elements .

Intermediate Operations:

  1. At each operation, state flags are utilized for optimization or modified if the state of the stream changes or added
  2. These flags are used for optimization at each stage, like if the stream is already SORTED at the previous stage then won’t be sorted again.

Terminal Operation:

  1. It decides the flow depending upon stateless like filter(),map(),and flatMap() and stateful operations like sorted(), limit(), and distinct()
  2. If the pipeline is executing sequentially, or in parallel but composed of all stateless operations, it can be computed in a single pass. Otherwise, the pipeline is divided into sections at stateful operation boundaries and is computed in multiple passes.
  3. Terminal operations are either short-circuiting like limit(), findFirst() or non–short-circuiting like reduce(), collect(). If the terminal operation is non–short-circuiting, the data can be processed in bulk using the forEachRemaining() method of the spliterator. If it’s short-circuiting, it must be processed one element at a time using tryAdvance()

7. What are the Advantages of a Stream?

  1. Concise and cleaner code.
  2. You can easily read and understand code that has a number of complex operations.
  3. Rewriting of code is not required for parallel processing. Once identified just use a parallel stream on the source.
  4. Expressing a stream pipeline as a sequence of operations enables several useful execution strategies through the use of state flags.
  5. We can return streams from API methods, where previously you might have returned an array or collection. This will help the client code to collect streams as per requirement.

8. What is Functional Programming?

Let’s quickly understand it, Functional programming is a subset of the declarative style of programming

Declarative style vs usual Imperative Style?

In declarative style, you define what to do instead of how to do it. Check the simple example below.

Imperative Style: We define the variable total sum, iterate 10 numbers and add them to the total sum. Basically, we are defining how to add 10 numbers.

Declarative Style: Do the sum of the 10 numbers. Just declared It.

That’s it, Thanks!

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--

Software Engineer, I write about my experience with Java, Spring, Micro-services, gRPC, REST, Data Structures, Algorithms, and Software Engineering Interviews.