Java 8 Stream : Need with examples and Limitations

Reading Time: 4 minutes

Before starting with java 8 stream firstly look at this example that will illustrates the need of java 8 stream and the power of stream as well.


private static int sumIterator(List list) {
	Iterator it = list.iterator();
	int sum = 0;
	while (it.hasNext()) {
		int num = it.next();
		if (num > 10) {
			sum += num;
		}
	}
	return sum;
}

There are some issues with this approach :-

  1. Here we just want the sum of integers but we would also have to provide how the iteration will take place, this is also called external iteration because this  program is handling the algorithm to iterate over the list.
  2. This program is sequential in nature, so here parallel execution can’t take place.
  3. We have to write too much code for just this simple task.
  4. Time taking approach.

To overcome all these shortcomings, Java 8 Stream API was introduced. We can use Java Stream API to implement internal iteration, that is better because java framework is in control of the iteration.

Mostly java 8 Stream API method arguments are functional interfaces, so that is why lambda expressions work very well with them. Not take a look how we can write above logic in a single line statement using Java Streams.


private static int sumStream(List list) {
	return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}

What is Stream ?
Stream represents a sequence of objects from a source, which supports aggregate operations. Most of people think streams is like collection but streams are different from collections.   

The classes StreamIntStreamLongStream, and DoubleStream are streams over objects and the primitive intlong and double types. Streams  are  much differ from the collections in several ways:

  • Stream doesn’t store data, it operates on the source data structure (collection and array) and produce pipelined data that we can use and perform specific operations. Such as we can create a stream from the list or array and filter it based on a condition.
  • Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections. That is why we can say it is 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.
  • Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, “find the first String with three consecutive vowels” need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.In Java 8 Streams API, the intermediate operations are lazy and their internal  processing model is optimized to make it being capable of processing the large
    amount of data with high performance.
  • Consumable. This is not possible to reuse the same stream for multiple times .  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.
  • Functional in nature. Java 8 Stream operations use functional interfaces, that makes it good standard for functional progamming using lambda expression. An operation 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, instead of removing elements from the source collection.

Examples :
Let’s go with few example to see how actually streams works :


import java.util.Arrays;
import java.util.List;
public class Example1 {
 public static void main(String... args) {
 List myList = Arrays.asList("kunal","anshul", "abhinav",
 "abhishek", "himanshu");
myList
.stream().filter(s -> s.startsWith("abhi")).map(String::toUpperCase)
.sorted().forEach(System.out::println);
}
}

Stream have two types of operations that is either intermediate or terminal. Intermediate operations return a stream so we can chain multiple intermediate operations without using semicolons as we do in scala. Terminal operations are either void or return a non-stream result. In the above example filtermapand sorted are intermediate operations whereas forEach is a terminal operation. Such a chain of stream operations as seen in the above example is also known as operation pipeline.


import java.util.Arrays;

public class Example2 {
    public static void main(String... args) {

        Arrays.asList("Kunal", "Himanshu", "Anshul")
                .stream()
                .findFirst()
                .ifPresent(System.out::println);
    }
}

As seen in above example we can write same code with different way also. Calling the method stream() on a list of objects returns a regular object stream. But we don’t need to create collections in order to work with streams as we will see in the next code example:


import java.util.stream.Stream;

public class Example3 {
    public static void main(String... args) {

        Stream.of("Kunal", "Himanshu", "Anshul")
                .findFirst()
                .ifPresent(System.out::println);

    }
}

So for this approach you have to just use Stream.of() to create a stream from a collection of object references.


import java.util.stream.IntStream;

public class Example4 {
    public static void main(String[] args) {

IntStream.range(1,10)
        .forEach(System.out::println);

}
}

Besides regular object streams Java 8 ships with special kinds of streams for working with the primitive data types intlong and double. As we discussed at starting that is IntStreamLongStream and DoubleStream. As seen in above example IntStreams can replace the regular for-loop utilizing IntStream.range().

Limitations Of Streams :

  1. Once a Stream is consumed, it can’t be used later on. As you can see in above examples that every time I created a stream.
  2. Lambda expressions (as well as anonymous classes) in Java can only access to the final (or effectively final) variables of the enclosing scope.
  3. There are a lot of methods in Stream API and so the most confusing part is the overloaded methods. It makes the learning curve time taking. So  first time it takes some time to learn about how it works.
  4. Stateless lambda expressions: If you are using parallel stream and lambda expressions are stateful, it can be result in random responses. Let’s see this  with a simple program.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ParallelStreamExample {
    public static void main(String[] args) {

        List list = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
        List result = new ArrayList();

        Stream stream = list.parallelStream();

        stream.map(val -> {
            synchronized (result) {
                if (result.size() < 12) { 
                      result.add(val);            
                   } 
                }   
                  return val; }).forEach( e -> {});
        System.out.println(result);
    }
}

Refrences :
https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
https://www.journaldev.com/2774/java-8-stream


knoldus-advt-sticker


Written by 

Kunal Sethi is a Software Consultant with experience of more than 2.5 years. He is a Java enthusiast and has knowledge of various programming languages such as Scala, C++. He is familiar with Object-Oriented Programming Paradigms, loves to code applications in J2EE. He developed his own stand-alone application in Java based on the intranet chatting System and email system during his Master's Degree.

1 thought on “Java 8 Stream : Need with examples and Limitations5 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading