The Java 8 Stream API

In this post, we are going to cover Java 8 Stream in details. If you are starting with Java 8 Stream, please read Introduction to Java 8 Streams for the basic overview.

 

Introduction

Java Stream API was introduced in Java 8. ‘Stream’ is actually a sequence of objects with from a source that supports different operations to be pipelined to produce a result. In this tutorial, we will go through the different usage of Java 8 Stream.

 

1. Difference between Collection and Stream

Following are the major differences between Collection and Stream API in Java:

  1. A collection is a data structure. It holds values or elements and all elements need to be populated before we start to use a collection. But, the stream is not a data structure like ‘collection’. It computes the elements from a source on demand using a pipeline of computational operations.
  2. We can traverse through a collection as many times as we want. But we cannot traverse through a Stream more than once. We need to create the stream again to revisit the same elements.
  3. ‘collection’ can hold only a finite number of elements. But stream can work on an infinite number of elements.
  4. Collection constructs values eagerly. On the other hand, Stream API creates values on demand, i.e. Stream creates values lazily.
  5. Stream API is relatively new as compared to Collection API. It was introduced in Java 8 whereas Collection was introduced in Java 1.2.

 

2. Stream Creation

We can create a stream from different data sources. Let’s have a look into different ways to create Java Stream:

 

2.1 Stream from Values

We can create Java Stream from a group of similar items using its ‘of’ static method. ‘of’ is an overloading method and it can take either one or multiple elements as a parameter. If only one element is passed as a parameter, it will create one stream with only one element and if multiple elements are passed, it will create one stream with multiple elements. In both cases, the stream is a sequential stream.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream stream = Stream.of(1);
  Stream < Character > charStream = Stream.of("a", "b", "c");
  Stream < String > strStream = Stream.of("Hello", "World");
 }
}

In the above example, ‘stream’ is a single element stream, charStream’ is a Stream of characters and ‘strStream‘ is a Stream of Strings.

 

2.2 Stream from String

String class introduced two different methods ‘chars()‘ and ‘codePoints()‘ to get one ‘IntStream’ from a string. ‘chars()‘ method returns an instance of IntStream from a string.

IntStream stream = "Hello World !!".chars();

This method is helpful if we want to work on the Unicode representation of the characters of a string. We can also use ‘codePoints()‘ method to get an ‘IntStream‘. This method returns a stream of code points values for the string characters.

IntStream stream = "Hello World !!".codePoints();

 

2.3 Create Stream from a Pattern

Pattern class comes with a method called ‘splitAsStream’ that creates one stream from a given input sequence using the pattern.

Pattern p = Pattern.compile(",");
StringBuilder stringBuilder = new StringBuilder("1,2,3,4,5");
Stream < String > stream = p.splitAsStream(stringBuilder);

 

2.4 Stream from Arrays

As shown in the previous example, we can use ‘Stream.of()‘ method to create a stream of an Array. The only problem with this method is that we cannot pass any primitive type array.

class Main {
    public static void main(String[] args) {
        Stream intStream = Stream.of(new Integer[]{1,2,3,4,5});
    }
}

If you will try to pass ‘new int[]{1,2,3,4,5}‘ in the example above, it will show you one compile-time error. Instead, we can use ‘Arrays.stream()‘ method to create Stream from an array. We can use primitive types with this method.

import java.util.Arrays;
import java.util.stream.IntStream;

class Main {
    public static void main(String[] args) {
        IntStream intStream = Arrays.stream(new int[]{1,2,3,4});
    }
}

This method returns a specialized stream if the array is of primitive types. For int array, it returns IntStream, for double array DoubleStream and for a long array, it returns LongStream.We can also create one ‘IntStream’ using one part of the given array by passing two more extra values as the parameter to the ‘stream’ method.

int[] mainArray = new int[]{1,2,3,4,5,6,7};
IntStream s = Arrays.stream(mainArray,2,5);

The result stream ‘s’ is created starting from the second index element to the fourth index element of the array ‘mainArray’.

 

2.5 Stream from Collection

Creating a Java Stream from a collection is easier than other data sources. One new method called ‘stream()‘ is added to the Collection interface in Java 8. It returns one sequential stream.

class Main {
 public static void main(String[] args) {
  List < String > wordList = new ArrayList < > ();
  wordList.add("Hello");
  wordList.add("World");
  Stream wordStream = wordList.stream();
 }
}

‘wordStream’ is a sequential stream. We can also create one parallel stream using ‘parallelStream’ method.

Stream parallelWordStream = wordList.parallelStream();

We can use these methods on any type of collection like ‘list’,’set’ or ‘queue’, e.g. the below example is to create one stream from a ‘Set’:

Set set = new HashSet();
set.add("First element");
set.add("Second element");
set.add("Third element");

Stream stream = set.stream();

 

2.6 Creating Empty Stream

The empty() method is used to create a Stream with no elements Stream emptyStream = Stream.empty();. We can also create one empty stream like below

Stream emptyStream = Stream.of();

The value of ‘emptyStream.count()‘ is ‘0’ for both of these examples.

 

2.7 Stream from generate() and iterate()

Using a static method generate(), we can get an infinite sequential unordered stream. For example :

Stream.generate(new Random()::nextInt).limit(6).forEach(System.out::println);

It will print six random integers like below:

-1655505878
-1401904709
1294002467
-565795232
199853859
-1596455217

This is useful for generating a stream of constant elements or stream of random values. Similarly, using a static function iterate(), we can have infinite sequential ordered Stream. This function takes two parameters: the first parameter is an initial value and the second parameter is a function. This function is applied to the previous element to produce the next. e.g.

Stream.iterate(3, n -> n + 1).limit(6).forEach(System.out::println);

It prints below values

3
4
5
6
7
8

The first value of ‘n’ is ‘3’, so it printed ‘3’ first. Then it incremented the value by ‘1’ for each iteration. If we will not use the ‘limit’, it will run for an infinite amount of time.

 

2.8 Stream from files

The java.nio.file.Files class contains several methods to create a stream of different file contents. We can use 'lines(Path path)' static method to get all lines of a file as a Stream. For example:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

class Main {
 public static void main(String[] args) throws IOException {
  Stream < String > fileStream = Files.lines(Paths.get("C:\\sample.txt"));
  fileStream.forEach(System.out::println);
 }
}

This program will print all lines of the file “sample.txt“. We can also use ‘filter’ on a stream to find a specific file. For example, the below program will print the first line that contains the word ‘stream’:

class Main {
 public static void main(String[] args) throws IOException {
  Optional < String > lineWithStreamWord = Files.lines(Paths.get("C:\\sample.txt")).filter(s -> s.contains("stream")).findFirst();
  if (lineWithStreamWord.isPresent()) {
   System.out.println(lineWithStreamWord.get());
  }
 }
}

 

3. Stream Conversion

We can convert a Stream to a Collection or to an Array.

 

3.1 Stream to a List

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > numStream = Stream.of(1, 2, 3, 4, 5, 6, 7);
  List < Integer > numList = numStream.collect(Collectors.toList());
  System.out.println(numList);
 }
}
//Output
[1, 2, 3, 4, 5, 6, 7]

We can also use ‘Collectors.toCollection‘ method in the above example :

List numList  = numStream.collect(Collectors.toCollection(ArrayList::new));

Or, we can use ‘forEach’ method to go through the elements one by one and add them to a new list.

Stream numStream = Stream.of(1,2,3,4,5,6,7);
List numList = new ArrayList<>();
numStream.forEach(numList::add);

 

3.2 Stream to a Map

Similar to the above example, we can also use ‘collect()‘ method to convert a stream to a Map.

Stream<String[]> mapStream = Stream.of(new String[][]{{"1", "one"}, {"2", "two"}});
Map<String, String> finalMap = mapStream.collect(Collectors.toMap(e -> e[0], e -> e[1]));
System.out.println(finalMap);
//Output
{1=one, 2=two}

Similarly, we can also convert Stream to a ‘Set’ using the ‘collect()‘ method.

 

3.3 Stream to an Array

For converting Stream to Array, we can use ‘toArray(IntFunction<A[]> generator)‘ static function. It returns an Array containing the elements of the provided Stream:

Stream intStream = Stream.of(1, 2, 3, 4, 5, 6);
Integer[] intArray = intStream.toArray(Integer[]::new);
System.out.println(Arrays.toString(intArray));

The output of the above program is:[1, 2, 3, 4, 5, 6]. This method actually takes the size of the stream as an argument and returns one Integer array. We can also use ‘mapToInt‘ to convert the above stream to an array like below :

Stream intStream = Stream.of(1, 2, 3, 4, 5, 6);
int[] intArray = intStream.mapToInt(i -> i).toArray();

 

4. Stream Operation

Stream operations are divided into intermediate and terminal operations. Intermediate operations return a new stream and terminal operation traverse a stream to produce an end result.

 

4.1 Stream.filter()

The filter() is used to filter out elements from a Stream. It returns a new Stream.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4, 5, 6);
  Stream < Integer > evenStream = intStream.filter(e -> (e % 2 == 0));

  evenStream.forEach(e -> System.out.print(e + " "));
 }
}

This example will print ‘2,4,6’ as output.

 

4.2 Stream.map()

The map() is used to convert each element of a Stream to a different object. e.g.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4, 5, 6);
  Stream < Integer > mapStream = intStream.map(e -> e * 10);

  mapStream.forEach(e -> System.out.print(e + " "));
 }
}

It prints ’10 20 30 40 50 60 ‘ i.e. each element of the first stream was multiplied by 10.

 

4.3 Stream.sorted()

The sorted() is used to sort the elements of a stream in a natural order. We can also pass a custom comparator to this method to customize the sorting order.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(4, 22, 1, 7, 5, 10);
  Stream < Integer > sortedStream = intStream.sorted();

  sortedStream.forEach(e -> System.out.print(e + " "));
 }
}

It will print ‘1 4 5 7 10 22 ‘ as output.

 

4.4 Stream.forEach()

This method is to iterate over the elements of a stream. We can iterate and perform specific operations on each element of a stream.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4, 5, 6, 7);
  intStream.forEach(e -> System.out.print(e * 3 + " "));
 }
}

It prints ‘3 6 9 12 15 18 21’.Similarly, we can use it on a map :

Map < String, Integer > map = new HashMap < > ();

map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
map.put("d", 4);

map.forEach((key, value) -> System.out.println("Key: " + key + " Value: " + value));

Output is:

Key: a Value: 1
Key: b Value: 2
Key: c Value: 3
Key: d Value: 4

 

4.5 Stream.reduce()

The reduce() is used to perform ‘reduction’ on the Stream elements. It returns an Optional holding the result value.

import java.util.Optional;
import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4);

  //reduce will add each element of the stream
  Optional < Integer > optionalResult = intStream.reduce((i, j) -> i + j);

  System.out.println(optionalResult.get());
 }
}

The output is ’10’ for the above program.

 

4.6 Stream.count()

The count() is used to find out the total number of elements in a Stream.

import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4);
  System.out.println("Total elements " + intStream.count());
 }
}

It prints ‘Total elements 4’ as output. If the stream is an empty stream, it will print zero.

 

4.7 Stream.collect()

The collect() is used to convert a stream to a different collection.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Main {
 public static void main(String[] args) {
  Stream < Integer > intStream = Stream.of(1, 2, 3, 4, 5, 6, 7);
  List < Integer > intList = intStream.collect(Collectors.toList());
  System.out.print(intList);
 }
}

The out is '[1, 2, 3, 4, 5, 6, 7]'. As you can see that the ‘intStream‘ is converted to a list of Integers using collect.

 

4.8 Stream.match()

We can use ‘allMatch’, ‘anyMatch’ or ‘noneMatch’ method with a Stream. Each of them takes one ‘Predicate’ as a parameter and returns one boolean.

import java.util.ArrayList;
import java.util.List;

class Main {
 public static void main(String[] args) {
  List < String > strList = new ArrayList < > ();
  strList.add("Sun");
  strList.add("Mon");
  strList.add("Tues");

  //check if any value in the list starts with 'T'
  System.out.println(strList.stream().anyMatch(e -> e.startsWith("T")));

  //check if all values in the list start with 'T'
  System.out.println(strList.stream().allMatch(e -> e.startsWith("T")));

  //check if none of the values in the list starts with 'X'
  System.out.println(strList.stream().noneMatch(e -> e.startsWith("X")));
 }
}

Output:

true
false
true

 

5. Parallel Java Stream

We can enable parallelism in Java 8 Stream operation using parallel Stream. It improves the runtime performance by allowing multiple Streams to run parallelly. Note that the system should support parallel programming to use parallel Stream operation.

Collection API provides one method parallelStream() to obtain a parallel Stream. For other sequential streams, we can use parallel() method to convert it to a parallel Stream.

import java.util.ArrayList;
import java.util.List;

class Main {
 public static void main(String[] args) {
  List < Integer > intList = new ArrayList < > ();

  intList.add(1);
  intList.add(2);
  intList.add(3);
  intList.add(4);
  intList.add(5);

  intList.parallelStream().
  filter(e -> {
   System.out.println("filter " + e);
   return true;
  }).
  forEach(e -> System.out.println("ForEach " + e));
 }
}

The output is:

filter 3
filter 5
ForEach 5
ForEach 3
filter 2
filter 1
ForEach 2
filter 4
ForEach 4
ForEach 1

As the stream is parallel, the ‘filter’ operation for each element will execute parallelly. So, the output will be different for each execution of the program. This method comes in handy if you want to perform independent tasks on a list of stream elements parallelly. If you use ‘stream()’ instead of ‘parallelStream()‘, the output will print in a sequential order :

filter 1
ForEach 1
filter 2
ForEach 2
filter 3
ForEach 3
filter 4
ForEach 4
filter 5
ForEach 5

The output will be the same for each execution of the program.

 

6. Stream operation sequence and performance

Processing stream in order doesn’t depend on its execution type. For example, if we are performing an operation that maintains the order of execution, it will always execute in order either it is parallel or sequential. It depends on the type of the source and the intermediate operations. For example, List and arrays are intrinsically ordered stream source but HashSet is not.

class Main {
 public static void main(String[] args) {
  Set < Integer > intSet = new TreeSet < > (Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

  Object[] array = intSet.stream().parallel().limit(8).toArray();
  Object[] array1 = intSet.stream().unordered().parallel().limit(8).toArray();
  System.out.println(Arrays.toString(array));
  System.out.println(Arrays.toString(array1));
 }
}

Output:

[1, 2, 3, 4, 5, 6, 7, 8]
1, 2, 3, 4, 6, 8, 9, 10]

The first output maintained the order but the second one doesn’t. For a sequential stream, the performance will be same either it is ordered or unordered. But for an unordered stream, parallel execution will perform better result than sequential.

 

Summary

In this tutorial, we have looked at the difference between Java 8 stream and collection, different ways to create a Java stream, stream conversion, and different Java 8 stream operations. We have also learned about parallel stream and stream operation sequence. Download source code for this article from GitHub.

2 thoughts on “The Java 8 Stream API”

Comments are closed.