Java Streams: stream creation with examples

Streams let us do computation on the collection of data in a declarative way. In a declarative way of programming, you don’t specify how to do but you specify what to do. You can create a stream pipeline to perform a computation. A stream pipeline consists of :

  • a source
  • zero or more intermediate operations
  • a terminal operation

Java streams are lazy. As a result, intermediate operations are performed only when the terminal operation is initiated.

Java stream pipeline

Let’s consider an example of finding all Java books author from the catalog of books. Using stream declarative construct you can write code as:


List<Book> books = 
  Catalog.books();

List<String> javaAuthors =
  books.stream()
  .filter(book -> book.getCategory().equals(JAVA))
  .map(Book::getAuthor)
  .collect(Collectors.toList());

Let’s try to understand the above stream pipeline in the context of – ‘a stream pipeline consists of a source, zero or more intermediate operations, and a terminal operation

Stream Source

in Java source of the stream could be an array, a collection, a generator function, an I/O channel, value range, etc. In the above example stream is created by calling stream() method of the Collection class.

Intermediate Operation

An intermediate operation transforms a stream into another stream. The intermediate operations are always lazy. Executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream. The new stream when traversed contains the elements of the initial stream that match the given predicate. Here, you have used filter(Predicate) to filter out books by category. The map(Function) transforms stream of books to stream of authors.

Terminal Operation

A terminal operation produces a result or side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used. Here, collect is a terminal operation. This accumulates input elements into a List.


Stream Operation
A stream pipeline consists of a Source, one or many intermediate, and a terminal operation

Code sample

Let’s see some stream creation examples. You can find the code of this article on GitHub .

Creating a stream

There are many ways you can create a stream in Java. Let’s look at a few of them.

Stream from individual elements

Stream of(T… values) – returns a sequential ordered Stream whose elements are the specified values.


Stream<String> numbers = 
  Stream.of("One", "Two", "Three");

All streams operations can execute either in serial or in parallel. The stream implementations in the JDK create serial streams unless parallelism is explicitly requested. For example, Collection has methods Collection.stream() and Collection.parallelStream(), which produce sequential and parallel streams respectively; other stream-bearing methods such as IntStream.range(int, int) produce sequential streams but these streams can be efficiently parallelized by invoking their BaseStream.parallel() method.

JDK stream documentation

Stream ofNullable(T t) – return a sequential stream containing a single element, if non-null, otherwise returns an empty stream.


Stream<String> numbers = 
  Stream.ofNullable("One");

Stream<String> empty = 
  Stream.empty(); 

The method Stream.ofNullable provides a simplified null check. For example – if you need to convert Collection<T> to Stream<T> you can do something like:


static Stream<Integer> toStream(Collection<Integer> numbers) {
   return Stream.ofNullable(numbers)
  .flatMap(Collection::stream);
}

The method ofNullable(T t) was added in Java 9.

An alternative approach can be to use optional as:


return Optional
  .ofNullable(numbers)
  .stream()
  .flatMap(Collection::stream);

Stream from Array

You can create a sequential Stream with the specified array as its source as:


String[] names = 
  new String[] {"Jack","Jill"};

Stream<String> stream1 = 
  Stream.of(names);
 
Stream<String> stream2 = 
  Arrays.stream(names);

Stream from a Collection

A sequential Stream can be created by calling the default stream method of collection.


List<String> namesList = 
  List.of("Jack", "Jill");

Stream<String> stream = 
  namesList.stream();

Stream from a Builder

Using builder you can build an ordered stream by calling add and build method.


Stream<String> stream = 
  Stream.<String>builder()
  .add("Jack")
  .add("Jill")
  .build();

Stream using Generate

You can use generate method to create an infinite sequential unordered stream where each element is generated by the provided Supplier. This is suitable for creating a constant stream, a stream of random elements, etc.


Stream<Integer> randomNumbers = 
  Stream
  .generate(new Random()::nextInt)
  .limit(10);

Stream using Iterate

You can create an infinite sequential ordered stream by calling a static factory method iterate (Stream iterate(final T seed, final UnaryOperator f)). The first element of the Stream is determined by seed and for n > 0, the element at position n will be the result of applying the function f to the n-1.


Stream<Integer> even = 
  Stream
  .iterate(0, n -> n + 2)
  .limit(10);

Primitive Stream

Specialized primitive interfaces IntStream, DoubleStream, and LongStream were added as part of Java 8 to handle primitive operations efficiently.

Let’s try to understand the need for primitive specialization with one example. Consider a Book class


@Setter
@Getter
public class Book {
  private String name;
  private Category category;
  private double price;
  private String author;
  private String publisher;
}

To find the accumulative price of all the books, you can do something like:


static double priceOfAllBooks(List<Book> books) {                              
  Double allBooksPrice = 
  books.stream()
  .map(Book::getPrice)
  .reduce(0d, Double::sum);

  return allBooksPrice;                                                                     
}                                                                                           

In the above example, you first transform the stream of the book into the stream of Double by calling map(Book::getPrice) then the sum is calculated by calling terminal operation reduce(0d, Double::sum).

You can simplify this example by transforming Stream<Book> to DoubleStream as:


static double priceOfAllBooks(List<Book> books) {                   
  double allBooksPrice = 
  books.stream()
  .mapToDouble(Book::getPrice)
  .sum();  

  return allBooksPrice;                                                     
}                                                                           

In the above example mapToDouble is an intermediate operation, which returns a DoubleStream. The DoubleStream is a stream of double, which defines handy methods such as sum,min,max,etc.

Java has three primitive streams – IntStream, DoubleStream, LongStream to perform stream operation int, double and long primitive type.

Creating Primitive Streams

You can create a primitive stream from a regular stream using methods from the Stream class. For example, mapToDouble method converts a stream to DoubleStream. Similarly, the Stream class also has mapToInt and mapToLong method.

Let’s look at the example of creating primitive streams.

Of

You can create a Sequential ordered Stream using the static factory method of as:


IntStream intOne = 
  IntStream.of(1);

IntStream intOneTwo = 
  IntStream.of(1,2);

DoubleStream doubleOne = 
  DoubleStream.of(1);

LongStream longOneTwo = 
  LongStream.of(1,2); 

Range

You can use the range method from primitive specialization to create a sequential ordered stream.


IntStream oneToNine = 
  IntStream.range(1, 10);

// Range inclusive
IntStream oneToTen = 
  IntStream.rangeClosed(1, 10);

Generate

You can create an infinite sequential unordered primitive stream using by using the generate method of primitive specialization stream. This is suitable for generating constants streams, streams of random numbers, etc.


IntStream tenOnes = 
  IntStream.generate(() -> 1).limit(10);

DoubleStream tenRandomDouble = 
  DoubleStream
  .generate(() -> new Random()
  .nextDouble()).limit(10);

Generating Fibonacci numbers

You can use generate method for some interesting use cases. For example, the Fibonacci numbers can be generated as:


public class Fibonacci {
  private int prev = 0;
  private int curr = 1;

  private int next() {
    int temp = prev + curr;
    prev = curr;
    curr = temp;
    return curr;
  }

  public IntStream stream() {
    return IntStream
    .generate(this::next);
  }
}

//Caller
Fibonacci fibonacci = 
  new Fibonacci();

IntStream fibStream = 
  fibonacci.stream().limit(10);

Iterate

You can create an infinite sequential ordered stream using the iterate method. In IntStream class, the iterate method is defined as IntStream iterate(final int seed, final IntUnaryOperator f).

The iterate method produces an infinite stream by applying function f to an initial seed. This produces a Stream consisting of seed, f(seed),f(f(seed)), etc.


IntStream evenNumbers = 
  IntStream
  .iterate(0, n -> n + 2)
  .limit(10);

Other ways of creating Streams

There are many other ways a Stream can be created. For instance, you can combine two streams to create a new stream. Let’s look at a few examples.

Concat

You can use concat operation of the stream to create a new stream. The elements of the new stream are all the elements of the first stream followed by all the elements of the second stream. The new stream is ordered if both of the input streams are ordered, and parallel if either of the input streams is parallel. If the resulting stream is closed, then both input streams are closed as well.


Stream<Integer> prime = 
  Stream
  .concat(Stream.of(2, 3), 
    Stream.of(5, 7, 11));

We need to exercise caution with nested concatenation, Stream.concat(Stream.of(..), Stream(Stream.of(..),Stream.of(..)), as it can result in a deep call chain or even worse StackOverflowError.

String

Java 8 added utility methods to create a stream from the String object.

Lines

You can create a stream of lines separated by line terminator as:


static Stream<String> lines() {                       
  return "Line separated by newline.\nAnother line."
  .lines(); 
}                                               

The above example creates a stream consisting of two elements:

  1. Line separated by newline.
  2. Another line.

Chars

You can use chars method of String class to create IntStream of chars, as:


static IntStream chars() { 
  return "XYZ".chars();            
}                                  

Random

The Random class has a couple of useful methods to generate streams of random numbers of primitive specialization types.


// Infinite Stream                                  
IntStream randomInts = 
  new Random().ints();
         
// Fixed Size Stream                                
IntStream tenRandomInts = 
  new Random().ints(10);   
 
// Infinite Stream                                  
DoubleStream randomDoubles = 
  new Random().doubles();

// Fixed Size Stream                                
DoubleStream tenRandomDoubles = 
  new Random().doubles(10);

// Infinite Stream                                  
LongStream randomLongs = 
  new Random().longs();      

// Fixed Size Stream                                
LongStream tenRandomLongs = 
  new Random().longs(10); 

Summary

Streams let us do computation on the collection of data in a declarative way. You can create a stream pipeline to perform a computation. A stream pipeline consists of :

  • a source
  • zero or more intermediate operations
  • a terminal operation

There are many ways a stream can be created. For instance, you can create steam from an array, collection, or individual elements. You can even create a stream using generate, iterate, or builder method of the Stream class.

Java 8 also added specialized primitive streams. For instance, IntStream, DoubleStream, and LongStream are primitive streams.

The primitive stream can be created from individual elements and using the range method of the primitive stream class. Similarly, the primitive stream can be created using a builder, iterate or generate method.

Social Share !
Default image
Pankaj
Software Architect @ Schlumberger ``` Cloud | Microservices | Programming | Kubernetes | Architecture | Machine Learning | Java | Python ```