What are Java Streams?

A Java stream is a sequence of elements of a specific type that are consumed from a source like Collections, arrays, or I/O resources. Streams are similar to collections in that they can both be used to process and aggregate data. However, there are some big differences.

Although the idea of “streams” has existed in previous Java versions, this post refers to the streams that implement the Stream interface in the java.util.stream package. Click here to learn more about the Java Stream interface.

Difference between Collections and Streams

Storage – Java Streams unlike collections do not store any data.
Computation – Elements in collections are calculated before being added to the collection, while stream elements are computed on demand as they are consumed.
Size – Collections contain a finite number of elements, while streams are potentially infinite.
Consumption – Elements in a stream can only be consumed once, similar to an iterator. Elements in a collection can be consumed as many times as desired.
Iteration – Iterating over collections is done externally (explicitly by the user) while steams use internal iteration and handle this for you.

Creating a Stream from a Collection

Since Java 8 the Collection interface has the stream() method which will return a stream using the collection as its source. Here is an example of creating a stream of Strings from a collection and printing each element.

Arrays.asList("a", "b", "c").stream().forEach(System.out::println);

Creating a Stream from File

Java Streams can also be created from files. The example below creates a Java stream from a file as it source and prints each line. The example below is created with try-with-resources statement to ensure that the stream is closed once processing is complete.

try(Stream<String> stream = Files.lines(Paths.get(filename))) {
   stream.forEach(System.out::println);
}
catch(IOException e) {}

Intermediate vs Terminal Operations

Stream intermediate operations are ones that return another Stream. This means that multiple intermediate operations can be used together (a.k.a. pipelining). Common intermediate operations include map, filter, and distinct.

Stream terminal operations result in the end of a stream by returning something other than a stream or nothing at all. Common terminal operations include forEach and collect.

Using Streams Together (Pipelining)

Many Stream methods return a Stream themselves. This means that streams can be chained together to perform different processing tasks. Chaining multiple streams together is often referred to as pipelining.

Here is an example of pipelining a few of streams together by first creating an stream from a list of several words, then using filter to return a stream of elements that contain 3 characters, and then using filter again to return a stream that contains only words that start with “c”.

Arrays.asList("cat","dog","mouse").stream().filter(t -> t.length() == 3).filter(t -> t.startsWith("c"));

Parallel Streams

Java Streams can be easily parallelized to leverage the multi core architectures in modern computers. You can create a parallel stream by calling parallelStream() on a Collection or parallel() on an existing stream.

Here is an example of creating a parallel stream from a collection. For each element in the stream, the code execution will pause for 1 second before printing the element. As this code executes, it is easy to see that multiple sub streams are being processed at once.

//populate collection with 10 elements to create stream from
List<Integer> myList = new ArrayList<Integer>();
for(int i=0; i<10; i++) {myList.add(i);}

//create parallel stream
myList.parallelStream().forEach(
  t -> {
  //sleep 1 second for each element
  try {
    Thread.sleep(1000l);
  } 
  catch (InterruptedException e) {e.printStackTrace();}
  
  //print 
  System.out.println(t);
 });

Leave a Reply

Introduction to Java Streams