Stream is a sequence of data that you can process in a declarative and functional style. The Stream interface is located in the java.util.stream package. It represents a sequence of objects somewhat like the Iterator interface. However, unlike the Iterator, it supports parallel execution.The Stream interface supports the map/filter/reduce pattern and executes lazily, forming the basis (along with lambdas) for functional-style programming in Java 8.
There are also corresponding primitive streams (IntStream, DoubleStream, and LongStream) for performance reasons. Let’s take a simple example of Iterating through a List with aim of summing up numbers above 10. This laziness is achieved by a separation between two types of operations that could be executed on streams: intermediate and terminal operations.
private static int sumIterator(List<Integer> list) { Iterator<Integer> it = list.iterator(); int sum = 0; while (it.hasNext()) { int num = it.next(); if (num > 10) { sum += num; } } return sum; }
The Disadvantages of above method are
- The program is sequential in nature, there is no way we can do this in parallel easily.
- We need to provide the code logic for sum of integers on how the iteration will take place, this is also called external iteration because client program is handling the algorithm to iterate over the list.
To overcome the above issue Java 8 introduced Java Stream API to implement internal iteration, that is better because java framework is in control of the iteration.Internal iteration provides several features such as sequential and parallel execution, filtering based on the given criteria, mapping etc.Java 8 Stream API method arguments are functional interfaces, so lambda expressions work very well with them. Using Stream the same code turnout to be
private static int sumStream(List<Integer> list) { return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum(); }
Streams are lazy because intermediate operations are not evaluated unless a terminal operation is invoked. Each intermediate operation creates a new stream, stores the provided operation/function and return the new stream. The pipeline accumulates these newly created streams.The time when terminal operation is called, traversal of streams begins and the associated function is performed one by one. Parallel streams don’t evaluate streams ‘one by one’ (at the terminal point). The operations are rather performed simultaneously, depending on the available cores.
To perform a sequence of operations over the elements of the data source and aggregate their results, three parts are needed –
- Source
- intermediate operation
- terminal operation
How to create a simple stream
Collection<String> collection = Arrays.asList("a", "b", "c"); Stream<String> streamOfCollection = collection.stream();
Stream<String> streamOfArray = Stream.of("a", "b", "c"); String[] arr = new String[]{"a", "b", "c"}; Stream<String> streamOfArrayFull = Arrays.stream(arr); Stream<String> streamOfArrayPart = Arrays.stream(arr, 1, 3);
Using Stream.builder() When builder is used the desired type should be additionally specified in the right part of the statement, otherwise the build() method will create an instance of the Stream Object
Stream<String> streamBuilder = Stream.<String>builder().add("a").add("b").add("c").build();
Using Stream.generate()
The generate() method accepts a Supplier
Stream<String> streamGenerated = Stream.generate(() -> "element").limit(10);
The code above creates a sequence of ten strings with the value – “element”.
Stream.iterate()
Another way of creating an infinite stream is by using the iterate() method:
Stream<Integer> streamIterated = Stream.iterate(40, n -> n + 2).limit(20);
The first element of the resulting stream is a first parameter of the iterate() method. For creating every following element the specified function is applied to the previous element. In the example above the second element will be 42.
A stream by itself is worthless, the real thing a user is interested in is a result of the terminal operation, which can be a value of some type or action applied to every element of the stream. Only one terminal operation can be used per stream.
For More details on streams refer here