Before starting Java Stream API let’s look why it was required. Suppose we want to iterate over a list of strings and want to count strings with length less than 5.
package com.w3spoint; import java.util.ArrayList; import java.util.List; public class Test{ public static void main(String[] args) { List<String> names = new ArrayList<String>(); names.add("Jai"); names.add("Mahesh"); names.add("Ajay"); names.add("Hemant"); names.add("Vishal"); int count = 0; for (String str : names) { if (str.length() < 5) count++; } System.out.println(count+" strings with length less than 5"); } } |
Output
2 strings with length less than 5 |
Problems with the above approach:
- We just want to know the strings with length less than 5 but we would also have to provide how the iteration will take place, this is also called external iteration because client program is handling the algorithm to iterate over the list.
- The program is sequential in nature, there is no way we can do this in parallel easily.
- There is a lot of code to do even for a simple task.
To overcome these limitations Java 8 introduced a new additional package called java.util.stream which consists of classes, interfaces and enum to allow functional-style operations on the elements. Java Stream API supports internal iteration. Internal iteration provides several features like sequential and parallel execution, filtering based on the given criteria, mapping etc.
The java.util.stream is a sequence of elements supporting sequential and parallel aggregate operations.
Java Stream Features
- Laziness-seeking: All the stream operations are lazy in nature which means they are not executed until they are needed. For example, if we want to get only the first 2 elements of a list using stream, the stream operation would stop at the end of second iteration after displaying the second element of list.
- No storage: A stream is not a data structure that stores elements. It simply conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
- Functional in nature: An operation performed on a stream produces a result, but does not modify its source. For example, filtering a Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
- Consumable: The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.
.
Let’s see the above discussed example using Java Steam API.
package com.w3spoint; import java.util.ArrayList; import java.util.List; public class Test{ public static void main(String[] args) { List<String> names = new ArrayList<String>(); names.add("Jai"); names.add("Mahesh"); names.add("Ajay"); names.add("Hemant"); names.add("Vishal"); //Using Stream and Lambda expression long count = names.stream().filter(str->str.length()<5).count(); System.out.println(count+" strings with length less than 5"); } } |
Output
2 strings with length less than 5 |
In the above example, the stream() method returns a stream of all the names. The filter() method returns another stream of names with length less than 5 and the count() method reduces this stream to the result. All these operations are happening parallelly which means we are able to parallelize the code with the help of streams. Parallel execution of operations using stream are much faster than sequential execution without using streams.