Chapter 9 - Java for Beginners Course

Creating Streams

What is a Stream?

The concept of streams in Java was introduced in version 8 and provides us with a different way of operating on sequences of elements of a given type.

You can think of a stream as a pipe that contains a sequence of elements. These elements can be processed/transformed/filtered based on a set of operations that we provide.

For example, let’s assume we have a Stream<Integer> that:

  • Contains the sequence 1, 2, 4 and 5

  • Where we want to filter the elements and only allow even numbers

  • And then we want to collect our results in an ArrayList

This can be represented as follows:

Example of an Integer Stream of 4 elements with a filter and a terminal operation

The 3 concepts in the diagram, which we’ll cover in these sections, make up the stream pipeline that define how our data will be processed.

Creating a Stream<T>

Java provides us with different mechanisms to create a Stream<T> of elements. In this section we’ll cover 3 of the most common approaches to create a Stream.

Using the Stream.of methods

The Stream interface provides us with static utility methods to create a stream from a single element or an array of elements we have. For example, to create the Stream<Integer> in our example above we could do the following:

Stream<Integer> numbers = Stream.of(1, 2, 4, 5);

Or, alternatively, if we already have an array, we can pass it into the of method:

Integer[] array = { 1, 2, 4, 5 };
Stream<Integer> numbers = Stream.of(array)

Using the stream() method in Collection types

All implementations of the Collection interface in Java (for example, ArrayList and others covered in chapter 7), provide a stream() method that will create a Stream with the elements in the collection.

For example, if we had a List<Integer> with the elements in the example we could do the following:

List<Integer> input = Arrays.asList(1, 2, 4, 5);
Stream<Integer> numbers = input.stream();

Creating IntStream instances

The Stream<T> interface we are discussing is meant for objects of any type, however, it isn’t meant for primitive types. Java provides specialized stream interfaces that work with primitive types, like IntStream.

For example, if we want a stream of int values that has the sequence 1, 2, 3, 4, we can write:

IntStream intStream = IntStream.range(1, 5);
The IntStream.range method doesn’t include the last number (it is exclusive on the end). If you want to include both the start and the end, you can use IntStream.rangeClosed.

If you need a Stream<Integer> based on an IntStream you can use the boxed() method as follows:

IntStream intStream = IntStream.range(1, 5);
Stream<Integer> integers = intStream.boxed();

Notes about Streams

In this section we covered a couple of options to create a Stream in Java. Once you have a Stream ready, there are a couple of things that are important to note:

  • Stream pipelines can have zero or more intermediate operations. We’ll cover this in the next section.

  • Streams are sequences of elements, hence, the elements are processed in the order they arrive. However, streams can be created as parallel streams in which case the processing order isn’t guaranteed.

  • Streams are lazy. This means that no processing will occur until you provide a terminal operation.

  • In general, a Stream shouldn’t be reused. If you need to reuse a Stream it is a good practice to create a new Stream instance.