Data structures such as arrays, sets, etc. are commonly used to store and manipulate data in Java. However, iterating over these data structures and performing operations on them can be cumbersome. Java 8 introduced a new feature called Streams, which allows us to process data in a declarative and functional way making it more safer and concise.
In this blog, we will discuss about the concept and working of Streams in Java with a delightful analogy that makes it super easy!
Stream
A stream is a way of processing a sequence of elements in a functional and declarative manner. A stream does not store the data itself, but only provides a view of the data source. This means that the data source is not modified by the stream operations.
Imagine Java Streams like a stream of vehicles passing through a toll gate. Each vehicle represents an element in the stream. Just as vehicles pass through a toll gate one by one, elements in a stream are processed sequentially or in parallel.
Java provides a Stream interface in the java.util.stream
package that defines the methods for creating and manipulating streams.
However, the Stream interface does not support primitive
types such as int
, long
, and double
. To use streams with these types, we need to use their corresponding wrapper classes Integer
, Long
, and Double
.
Creating an Empty Stream
The Stream
interface consists of a static
and default method empty()
that can be used to create an empty stream.
Creating an empty stream in Java is similar to having an empty toll lane at a toll gate. Just as an empty lane at a toll gate waits for vehicles to pass through, an empty stream in Java is ready to receive elements for processing. It serves as a placeholder, waiting to be filled with data before any operations can be performed on it.
In the above code, we have created an empty stream using the default method empty()
.
Creating a Stream with Collections
In Java, we can create a Stream
from a collection (such as a List, Set, or Map) using the stream()
method. Here’s how we can create a Stream from various types of collections:
Syntax:
collection.stream()
Example Syntax -
In the above code, we have created a list of Strings
and assigned it to myList
variable. From the list of Strings
we have created a stream and assigned it to the streamFromList
. Here, the stream does not occupy any memory, instead it just provides a way to access the original elements.
Direct creation of Stream
In Java, the Stream.of()
method is a convenient way to create a Stream from a sequence of elements. It allows us to create a Stream directly from individual elements or an array of elements without needing a collection.
Here’s how you can use the Stream.of()
method:
Stream.of(list of elements);
Example Syntax -
How to work with Streams?
Working with Streams involves the usage of some operations. These operations are basically divided as Intermediate Operations and Terminal Operations. Let us understand these terms easily using the same analogy of a Toll Booth.
Intermediate Operations
Intermediate operations are like toll booth workers who filter, transform, or manipulate the vehicles before they leave the toll gate.
These intermediate operations always produces a new stream
as output after performing the specified operations.
There are different types of intermediate operations:
filter()
-
map()
-
sorted()
-
distinct()
Filter()
Filtering allows us to select elements based on a certain condition.
Output -
Bike
Truck
Cycle
In the above code, we have created a stream of vehicles, filtered the names that have more than 3 letters and displayed the resultant names on the console.
Map()
Mapping helps to perform an operation on each element of the stream.
Output -
15
25
35
45
In the above code, the lambda expression eachNumber -> eachNumber + 5
is being used to map each element and increment each number by 5.
Sorted()
Sorting arranges elements in a specified order.
Output -
100
500
700
800
In the above code, the sorted()
method has arranged the elements of the numbers stream in ascending order.
Distinct()
Distinct method removes the duplicate elements from the stream.
Output -
Apple
Mango
In the above code, the distinct()
method has removed the duplicate strings from the stream.
Terminal Operations
Terminal operations are like the toll booth itself - once a vehicle passes the toll booth, it can't go back. Similarly, terminal operations are the final actions on a stream.
A terminal operation is a stream operation that consumes the elements of a stream and produces a result.
Types of terminal operations include:
-
forEach
reduce
-
count
ForEach()
This method is used for performing an action on each element.
Output -
Apple
Mango
Banana
Kiwi
In the above code, the forEach()
method is used to display the elements of the resultant stream on the console.
reduce()
The reduce()
is a terminal operation that combines all elements of the stream into a single result. It takes two parameters: an identity value and a Binary Operator (a function that takes two elements and produces a single result). The reduce()
operation repeatedly applies the Binary Operator to the current result and each element of the stream until all elements have been processed, producing a final result.
Let’s take an example to find the sum of all numbers in a list.
Output -
15
In this example, the reduce
operation starts with an initial value of 0 (identity
). For each element in the stream, it adds the element to the current sum (a + b
). The result is the sum of all elements in the stream, which is 15 in this case.
Use Case - reduce()
is useful when you want to aggregate elements of the stream into a single value, such as finding the sum, maximum, minimum, or concatenating strings.
count()
The count()
method returns a long
integer that indicates the number of elements present in the stream.
Output -
3
In the above code, the vehicles that has greater than 3 letters were filtered and the count()
method is used to get the number of vehicles in the resultant stream.
ForEach() vs Map()
Wondering what’s the difference between forEach()
and map()
methods? Though both these operations seem to be very similar, however they behave and operate differently.
The forEach()
method works similar to the map()
. It iterates over the elements in the stream and performs the specified operation on each element.
However, the only difference between forEach()
and map()
is -
- The
forEach()
method does not return any stream while themap()
method returns a stream. -
forEach
is used for performing actions on elements, whereasmap
transforms elements.
Use Case of map()
: Use map
when you want to transform the elements of the Stream from one form to another. For example, transforming strings to uppercase, converting objects to specific properties, or performing any other form of transformation.
Use Case of forEach()
: Use forEach
when you want to perform an action (such as printing, logging, or updating external states) for each element in the Stream.
Closing Thoughts:
Thank you for reading our blog. We appreciate your time and interest in our content. If you have any questions, feedback, or topics you’d like us to cover in our future articles, please feel free to leave a comment below. Your input is valuable, and it helps us create content that matters to you.
Stay connected with us for more insightful articles on various topics related to technology, programming, and much more.
Happy coding!
Top comments (0)