# Streams

A stream is a sequence of data element supporting sequential and parallel aggregate operations. An aggregate operation computes a single value from a collection of values. The result may be simply a primitive value, an object or a void.

Ex: Computing the sum of all elements in a stream of Integers, mapping all names in list to their lengths.

Difference between Collections and Streams:

* Collections focus on storage of data elements for efficient access.

* Streams focus on aggregate computations on data elements from a data source that can be a collection, a function that generates data, an I/O channel.

Features:

1) Streams have no storage.

* A stream pulls elements from a data source on-demand and passes them to a pipeline of operations for processing.

* A collection is an in-memory data structure that stores all its elements. All elements must exist in memory before they are added to the collection.

2) Streams can represent a sequence of infinite elements.

* Because a function can generate an infinite number of elements and a stream can pull data from it on demand, it is possible to have a stream representing a sequence of infinite data elements.

* A collection stores all its elements in memory, and therefore it is not possible to have an infinite number of elements in a collection. Infinite number of elements will require an infinite amount of memory and the storage process will continue forever.

3) Streams design is based on internal iteration.

* Specify a stream what we want by passing an algorithm using lambda expressions and the stream applies our algorithm to its data elements by iterating over its elements internally and gives us the result.

Ex: To compute the sum of squares of all odd integers in the list as follows,

List numbers =  Arrays.asList(1,2,3,4,5);
int sum = numbers.stream()
.filter(n -&gt; n % 2 == 1)
.map(n -&gt; n * n)
.reduce(0, Integer::sum);

Note: We did not iterate over the elements in the list. Streams provide an iterator() method that returns an Iterator to be used for external iteration of its elements. “Never” need to iterate elements of a stream using its iterator.

* We obtain an iterator for a collection and process elements of the collections in serial using the iterator as follows,

Ex:

int sum = 0;
for (int n : numbers) {
if (n % 2 == 1) {
int square= n * n;
sum = sum + square;
}
}

Note: The client code (the for-loop in this case) pulls the elements out of collection and applies the logic to get the result.

4) Streams can be processed in parallel with no additional work from the developers.

* We can compute the sum of cubes of odd integers in the list in parallel as follows,

Ex:

int sum = numbers.parallelStream()
.filter(n -&amp;gt; n % 2 == 1)
.map(n -&amp;gt; n * n)
.reduce(0, Integer::sum);

*Using external iteration in collections produces sequential code; i.e., the code can be executed only by one thread. For-each loop computing the sum must be executed only by one thread.

5) Streams support functional programming.

*The Streams API supports the functional/declarative programming – we specify only “what” we want in terms of stream operations and “how” part is taken care by Streams API. Operations on stream produce a result without modifying data source. When we use stream, we specify “what” operations we want to perform on its elements using the built-in methods provide by the Streams API, typically by passing a lambda expressions to those methods, customizing the behavior of those operations.

*Collections support imperative programming – we need to know “what” we want and “how” to get it.

This is an offshoot of collections supporting external iteration whereas streams support internal iteration.

6) Streams support lazy operations.

A stream supports two types of operations:
• Intermediate/Lazy/Result-bearing operations
• Terminal/Eager/Non result-bearing operations

Fig: A Stream Pipeline

Fig: States of a Sream Pipeline Operations

7) Streams can be ordered or unordered.

*A stream can be ordered or unordered. The Streams API  converts an ordered stream into an unordered stream and also vise versa by applying an intermediate operation such as sorting.

8) Streams cannot be reused.

*Unlike collections, streams are not reusable. They are one-shot objects. A stream cannot be reused after calling a terminal operation on it. If we need to perform a computation on the same elements from the same data source again, you must recreate the stream  pipeline. A stream implementation may throw an IllegalStateException if it detects that the stream is being reused.

Ex:

List list = Arrays.asList(1, 2, 3, 4, 5);
Stream stream = list.stream().filter(n -&gt; n % 3 == 0).map(n -&gt; n + 1);
stream.forEach(System.out::println);
stream.forEach(System.out::println);

Output:
1
3
5
Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed