Monday, 14 November 2016

Stream API in Java 8

If we have to point out the most important inclusion in Java 8 apart from lambda expression that has to be Stream API.

Stream API works in conjunction with lambda expression and provide an easy yet efficient way to perform data manipulation operations like sort, filter, map, reduce etc.

Stream in Stream API

A stream can be visualized as a pipeline. A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as filter(Predicate)), and a terminal operation (which produces a result or side-effect, such as count() or forEach(Consumer)).

Stream data flow

As shown in the figure beginning of the stream has a data source like an array, collection, file from there data moves through the stream where the stream operation like sorting, filtering etc. is performed on the data. Here note one important point that stream operations do not modify the data source in any way. Any stream operation results in a creation of a new stream.
As example if you are filtering a stream using some condition that will result in a creation of new stream that produces the filtered results.

Stream Example

At this point let’s see an example to actually see Stream API in action –

In this example objective is to take a list as an input and sort it taking only those elements of list which are greater than 5 and finally print it.

List<Integer> numList = Arrays.asList(34, 6, 3, 12, 65, 1, 8);
numList.stream().filter((n) -> n > 5).sorted().forEach(System.out::println);

Output

6
8
12
34
65

Here it can be seen that the list is the data source for the stream and there are two intermediate operations – filter and sorted. Filter condition here is; take only those elements of the list which are greater than 5, in next stream operation of the stream pipeline sort that filtered output of the last stream using sorted method of the Stream API. Terminal operation here is forEach statement (provided in Java 8) which iterates the sorted result and displays them.

How stream can be obtained

Streams can be obtained in a number of ways. Some examples include:

  1. From a Collection via the stream() and parallelStream() methods;
  2. From an array via Arrays.stream(Object[]);
  3. From static factory methods on the stream classes, such as Stream.of(Object[]), IntStream.range(int, int) or Stream.iterate(Object, UnaryOperator);
  4. The lines of a file can be obtained from BufferedReader.lines();
  5. Streams of file paths can be obtained from methods in Files;
  6. Streams of random numbers can be obtained from Random.ints();
  7. Numerous other stream-bearing methods in the JDK, including BitSet.stream(), Pattern.splitAsStream(java.lang.CharSequence), and JarFile.stream().

Types of Stream operations

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines.

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Terminal operations such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream.

Features of Stream

Some of the features of the stream are –

  • No storage - A stream is not a data structure that stores elements; instead, it 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 on a stream produces a result, but does not modify the data source. As example filtering a Stream obtained from a collection produces a new Stream without the filtered elements, rather than removing elements from the source collection.
  • Lazy behavior - Intermediate operations are always lazy. These operations do not start as soon as you reach that intermediate operation, it’s only when stream hits the terminal operation that it start executing operations.

    As example – if you execute the following code you won’t get any output as only filter operation is there which is an intermediate operation and it won’t execute unless until there is a terminal operation.

    List<Integer> numList = Arrays.asList(34, 6, 3, 12);  
    numList.stream().filter((n) -> {
        System.out.println("While filtering - " + n);
        return true;
    });
    

    If you add a terminal operation like forEach in this code then only both the operation will get executed.

    List<Integer> numList = Arrays.asList(34, 6, 3, 12);  
    List<Integer> numList = Arrays.asList(34, 6, 3, 12);  
    numList.stream().filter((n) -> {
        System.out.println("While filtering - " + n);
        return true;
    }).forEach(n -> System.out.println("forEach iteration - " + n));
    
  • Output

    While filtering - 34
    forEach iteration - 34
    While filtering - 6
    forEach iteration - 6
    While filtering - 3
    forEach iteration - 3
    While filtering - 12
    forEach iteration - 12
    

    Besides lazy execution also provides opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings.

  • Possibly unbounded - While collections have a finite size, streams need not. Short-circuiting operations such as limit(n) or findFirst() can allow computations on infinite streams to complete in finite time.
  • Consumable - The elements of a stream are only visited once during the life of a stream. Once terminal operation is executed that stream is deemed consumed and it can’t be used again. A new stream must be generated to revisit the same elements of the source.

Stateless and Stateful operations

Intermediate operations are further divided into stateless and stateful operations.

Stateless operations, such as filter and map, retain no state from previously seen element when processing a new element, each element can be processed independently of operations on other elements.

Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements. Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream.

That's all for this topic Stream API in Java 8. If you have any doubt or any suggestions to make please drop a comment. Thanks!

Reference - https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html


Related Topics

  1. Reduction Operations in Java Stream API
  2. Method reference in Java 8
  3. interface static methods in Java 8
  4. Optional class in Java 8
  5. String join() method in Java 8
  6. effectively final in Java 8

You may also like -

>>>Go to Java advance topics page

2 comments:

  1. I appreciate everything you have added to my knowledge base.Admiring the time and effort you put into your blog and detailed information you offer.Thanks

    Assignment writing service reviews

    ReplyDelete
  2. Very nice post on Stream API. What I like about your posts is those are neither too simple nor too complex that you can't make any sense out of those posts.
    Mostly all of your posts are well thought of and very nicely written. Thanks for that.

    ReplyDelete