HomeJavaUnderstanding Java 8 Streams with Examples

Understanding Java 8 Streams with Examples

Java 8 provided a new additional package called java.util.stream. This package consists of classes, interfaces and enum to allows functional-style operations on the elements. You can use stream by importing java.util.stream package. We can use Java Stream API to implement internal iteration. Internal iteration provides several features such as sequential and parallel execution, filtering based on the given criteria, mapping etc. In this journal entry we will be understanding java 8 streams with examples.

Understanding Java 8 Streams

Let’s look at some of the core concepts of Java 8 Stream API and then we will walk through some more examples of most commonly used methods.

  • Java Stream is a data structure that is computed on-demand.
  • Java Stream doesn’t store data. It operates on the source data structure and produce pipelined data that can be used to perform specific operations.
  • Most of the Java 8 Stream API method arguments are functional interfaces, so lambda expressions work very well with them.
  • Internal iteration principle helps in achieving lazy-seeking in some of the stream operations. For example filtering, mapping, or duplicate removal can be implemented lazily, allowing higher performance and scope for optimization.
  • Java 8 Stream support sequential as well as parallel processing, parallel processing can be very helpful in achieving high performance for large collections.
  • Java 8 Streams are consumable, so there is no way to create a reference to stream for future usage and reuse the same stream multiple times.

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


Spliterator interface is used in Java 8 Stream to support parallel execution. Spliterator trySplit method returns a new Spliterator that manages a subset of the elements of the original Spliterator.

Java 8 Streams operations and pipelines

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Intermediate Operations

  • Returns a new List.
  • 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.
  • 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.
  • Intermediate Operations are called Short Circuiting, if it may produce finite stream for an infinite stream. limit() and skip() are two short circuiting operations.

Terminal Operations

  • Operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect.
  • Once the terminal operations is performed, the Stream pipeline is considered consumed and can no longer be used
  • Almost all the cases of terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning.
  • Terminal operations iterator() and spliterator() are not eager; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.
  • Terminal operations are called as short circuiting, if it may terminate in finite time for an infinite stream. anyMatch(), allMatch(), noneMatch(), findFirst(), and findAny() are short circuiting terminal operations.

Java Stream Examples

We have tried to cover almost all the important parts of the Java 8 Stream API. Let’s see it in action with some java stream examples.

Java Streams Creation

Using Stream.of() to create a stream for similar type of data.

Stream<Integer> intStream = Stream.of(1,2,3,4);

We can use Stream.of() with array of objects also to create stream.

Integer intArray = new Integer(){1,2,3,4};

Stream<Integer> intStream = Stream.of(intArray);

We can use the collection stream() and parallelStream() to create sequential stream and parallel streams.

List<Integer> intList = new ArrayList<Integer>();

for (int k = 0; k < 1000; k++) { intList.add(k); }

// Sequential Stream
Stream<Integer> sStream = intList.stream();

// Parallel Stream
Stream<Integer> pStream = intList.parallelStream();

We can use Stream.generate() and Stream.iterate() to create Streams

Stream<String> stream1 = Stream.generate(() -> {return "developersjournal";});
Stream<String> stream2 = Stream.iterate("developersjournal", (i) -> i);

We can use Arrays.stream() and String.chars() also to create streams.

LongStream is = Arrays.stream(new long[]{1,2,3,4});
IntStream is2 = "developersjournal".chars();

Java Streams conversion to Collections or Array

We can use java Stream collect() method to get List, Map or Set from stream.

Stream<Integer> intStream = Stream.of(1,2,3,4);
List<Integer> intList = intStream.collect(Collectors.toList());

//prints [1, 2, 3, 4]

//stream is closed, so we need to create it again
intStream = Stream.of(1,2,3,4);
Map<Integer,Integer> intMap = intStream.collect(Collectors.toMap(i -> i, i -> i+10));

//prints {1=11, 2=12, 3=13, 4=14}

toArray() method can be used to create an array from stream

Stream<Integer> intStream = Stream.of(1,2,3,4);
Integer[] intArray = intStream.toArray(Integer[]::new);

//prints [1, 2, 3, 4]

In the next journal entry we will be looking at more Java 8 Stream examples.


Most Popular