Java 8 Streams

evertwagenaar.com logo

Java 8
Java 8

Java 8 Streams

In a previous article I already introduced you in the Java 8 lambda expressions. In this article I will get a step further and here I will tell you about Streams.

When I first read about the Stream API, I was confused about the name since it sounds similar to InputStream and OutputStream from Java I/O. But Java 8 streams are a completely different thing. Streams are Monads, thus playing a big part in bringing functional programming to Java:

In functional programming, a monad is a structure that represents computations defined as sequences of steps. A type with a monad structure defines what it means to chain operations, or nest functions of that type together.
This guide teaches you how to work with Java 8 streams and how to use the different kind of available stream operations. You’ll learn about the processing order and how the ordering of stream operations affect runtime performance. The more powerful stream operations reduce, collect and flatMap are covered in detail. The tutorial ends with an in-depth look at parallel streams.

This guide teaches you how to work with Java 8 streams and how to use the different kind of available stream operations. You’ll learn about the processing order and how the ordering of stream operations affect runtime performance. The more powerful stream operations reduce, collect and flatMap are covered in detail. The tutorial ends with an in-depth look at parallel streams.

If you’re not yet familiar with Java 8 lambda expressions, functional interfaces and method references, you probably want to read my Java 8 Tutorial first before starting with this tutorial.

How streams work

A stream represents a sequence of elements and supports different kind of operations to perform computations upon those elements:

List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);

// C1
// C2

Stream operations are either intermediate or terminal. Intermediate operations return a stream so we can chain multiple intermediate operations without using semicolons. Terminal operations are either void or return a non-stream result. In the above example filter, map and sorted are intermediate operations whereas forEach is a terminal operation. For a full list of all available stream operations see the Stream. Such a chain of stream operations as seen in the example above is also known as operation pipeline.

Most stream operations accept some kind of lambda expression parameter, a functional interface specifying the exact behavior of the operation. Most of those operations must be both non-interfering and stateless. What does that mean?

A function is non-interfering when it does not modify the underlying data source of the stream, e.g. in the above example no lambda expression does modify myList by adding or removing elements from the collection.

A function is stateless when the execution of the operation is deterministic, e.g. in the above example no lambda expression depends on any mutable variables or states from the outer scope which might change during execution.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.