Java 8 Streams

Java 8 comes with some prominent features like Lambda Expressions, Method References. And Streams are also an important concept that we should comprehend.

This tutorial will help you have a deep view of Java 8 Streams: what they are, ways to create them, how they work with intermediate operations, terminal operation…

I. Overview
1. What is Stream

A stream is an abstract concept that represents a sequence of objects created by a source, it’s neither a data structure nor a collection object where we can store items. So we can’t point to any location in the stream, we just interact with items by specifying the functions.

This is an example of a Stream:

Run the code above, the console shows:

Now, we have concept of using a Stream is to enable functional-style operations on streams of elements. Those operations are composed into a stream pipeline which consists of:
Source > Intermediate Operations > Terminal Operation
– a source (in the example, it is a collection – List, but it is also an array, a generator function, an I/O channel…)
intermediate operations (which transform current stream into another stream at the current chain, in the example, filter is the first operation and map is the second one)
– a terminal operation (which produces a result or side-effect, in the example, it is collect)

We will dive deeper into those things in the next parts of this tutorial.

2. Ways to create a Stream

We can obtain a stream in many ways with various kind of sources:
– From a Collection via the stream() and parallelStream() methods. The example above created a stream by this way:

– From an array via Arrays.stream(Object[]):

– From static factory methods on the stream classes: Stream.of(Object[]), IntStream.range(int, int), Stream.iterate(Object, UnaryOperator)…

– From methods in java.nio.file.Files:

– From methods in the JDK:

II. How a Stream works
1. Lazy

Streams are lazy. The Stream mechanism make all computations to the source data are only performed when the terminal operation is executed, and source elements are consumed only when they are needed.

Let’s return to the first example to understand how lazy it is.

Now we separate the terminal operation method collect into new statement, and insert a timer to delay the time after calling method map:

Check the result:

We can determine 2 clear things now:
– System only activates the command inside filter and map method:
{ System.out.println(...); return ...;}
after we call collect method.
– The intermediate operation, such as filter and map method, runs for each item which is passed into it immediately, and this operation is independent over the quantity of stream elements. If a item passes filter, it will come into the next method map without waiting for any item in the stream:
.....[5][4][3][2][1] stream >> intermediate operations .....
..[1] >> filter not pass
..[2] >> filter pass >> come into map without waiting for [3] to examine
..[3] >> filter not pass
..[4] >> filter pass >> come into map without waiting for [5] to examine

They all show the laziness of Stream’s behaviour.

2. Intermediate Operations

Each intermediate operation return another Stream which allows us to call next operation in a sequence. All operations of this kind will not be executed until a terminal operation is invoked.

filter
Stream<T> filter(Predicate<? super T> predicate)
filter returns a new stream which elements match the given predicate.
The example gets a new stream with even numbers from list of numbers:

map
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
map returns a stream which elements are the results of applying the given transform function.
The example gets a new stream which all number items are transformed from Integer to String.

flatMap
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
map is can only transform ONE object of a stream into exactly ONE object of another stream. But what if we wanna transform ONE into MORE or even NONE object?

flatMap can do that. Each object will be transformed into zero, one or multiple objects under streams’ formation.

Run the code, and the result in console:

Each Foo object is transferred into a stream of String by using flatMap method.
distinct
Stream<T> distinct()
distinct returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream.

Result:

sorted
Stream<T> sorted()
sorted returns a stream which elements are sorted according to natural order. If they are not Comparable, a java.lang.ClassCastException may be thrown when the terminal operation is executed.
If we wanna sort Objects with Custom Type, we can implement our own Comparator for the method:
sorted(Comparator<? super T> comparator)

Result:

limit
Stream<T> limit(long maxSize)
limit returns a stream which quantity of elements is limited to maxSize in length.

Result:

3. Terminal Operation

Terminal operation produces a result such as primitive value, a collection or a side-effect. After completing terminal operation, the stream pipeline is considered consumed, so it can no longer be used. If trying to invoke again using the consumed stream, it will throw a runtime exception:
java.lang.IllegalStateException: stream has already been operated upon or closed

So, if you need to traverse the same data source again, you must return to the data source to get a new stream.
forEach
void forEach(Consumer<? super T> action)
forEach performs an action for each stream element.
The example iterates over each element using forEach:

toArray
Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generator)
toArray returns an array containing stream elements.

Matching Method
Java 8 Streams has several matching methods that can be used for checking a provided predicate in each element of a stream. The methods’ names themselves indicate what they can do:
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)

collect
<R,A> R collect(Collector<? super T,A,R> collector)
collect transforms stream elements into another container such as a List. Java 8 supports many built-in collectors with class Collectors such as toList, groupingBy, toMap, joining. In most cases, we don’t need to implement any collectors.

reduce
reduce processes all elements of the stream and produces a single result.
reduce(BinaryOperator<T> accumulator)
reduce(T identity, BinaryOperator<T> accumulator)

min and max
Optional min(Comparator<? super T> comparator)
Optional max(Comparator<? super T> comparator)
min and max return the minimum or maximum element of the stream according to the provided Comparator.

III. Source code

Technology:
– Java 8
– Eclipse Mars.1 Release (4.5.1)
java8stream


Related Posts



Got Something To Say:

Your email address will not be published. Required fields are marked *

*