The Elegant Posture of Processing Sets in Java 8-Stream


In Java, collections and arrays are the data structures that we often use. They need to be added, deleted, changed, checked, aggregated, counted, filtered and so on. By contrast, these operations are also available in relational databases, but before Java 8, the processing of collections and arrays was not very convenient.

However, this problem has been improved in Java 8. The Java 8 API adds a new abstraction called Stream, which allows you to process data in a declarative manner. This article will introduce how to use Stream. In particular, the performance and principle of Stream is not the focus of this article. If you are interested, a separate article will be introduced later.

Stream introduction

Stream provides a high-order abstraction of Java set operations and expressions using an intuitive way similar to querying data from a database with SQL statements.

Stream API can greatly improve the productivity of Java programmers, allowing them to write efficient, clean and concise code.

This style considers the set of elements to be processed as a stream, which travels through the pipeline and can be processed at the nodes of the pipeline, such as filtering, sorting, aggregation, etc.

Stream has the following characteristics and advantages:

  • No storage. Stream is not a data structure, but a view of a data source, which can be an array, a Java container or an I/O channel, etc.
  • For functional programming. Any modification to Stream does not modify the underlying data source. For example, filtering on Stream does not delete the filtered elements, but generates a new Stream that does not contain the filtered elements.
  • Idle execution. Operations on Stream are not executed immediately, but only when the user really needs the results.
  • Consumability. Stream can only be “consumed” once, and once traversed it will fail, just like the container iterator, it must be regenerated to traverse again.

Let’s take an example to see what Stream can do.

In the example above, get some plastic balls with color as data source, first filter out the red ones and melt them into random triangles. Then filter and delete the small triangle. Finally, the circumference of the remaining figure is calculated.

As shown above, there are three key operations for flow processing: flow creation, intermediate operation and terminal operation.

The Creation of Stream

In Java 8, there are many ways to create streams.

1. Create flows through existing collections

In Java 8, in addition to adding many Stream-related classes, the collection class itself has been enhanced, in which a stream method has been added to convert a collection class into a stream.

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream<String> stream =;

Above, create a stream through an existing List. In addition, there is a parallelStream method that creates a parallel flow for collections.

This way of creating a Stream through collections is also a common way.

2. Create streams through Stream

You can use the methods provided by the Stream class to directly return a stream of specified elements.

Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");

As in the above code, a Stream is created and returned directly through the of method.

Stream Intermediate Operation

Stream has many intermediate operations, and many intermediate operations can be connected to form a pipeline. Each intermediate operation is like a worker on the pipeline. Every worker can process by convection, and the result after processing is still a stream.

Following is a list of commonly used intermediate operations:


The filter method is used to filter out elements by setting conditions. The following code snippet uses the filter method to filter out empty strings:

List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis"); -> !string.isEmpty()).forEach(System.out::println);
//Hollis, , HollisChuang, H, hollis


The map method is used to map each element to its corresponding result. The following code snippet uses map to output the corresponding square of the element:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); i -> i*i).forEach(System.out::println);


Limit returns the first n elements of Stream; skip throws away the first n elements. The following code snippet factorizes four elements using the limit method:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);;


The sorted method is used to sort convections. The following code snippets are sorted using the sorted method:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);;


Distinct is mainly used for de-duplication. The following code snippets use distinct to de-duplicate elements:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);;

Next, we demonstrate what happens when a Stream is processed successively by filter, map, sort, limit and distinct through an example and a graph.

The code is as follows:

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
Stream s = -> string.length()<= 6).map(String::length).sorted().limit(3)

The process and the results of each step are as follows:

Stream Final Operation

The result of Stream’s intermediate operation is still a Stream, so how can we convert a Stream into the type we need? Such as calculating the number of elements in the stream, changing the stream into a set, etc. This requires terminal operation.

The final operation consumes the flow and produces an end result. That is to say, after the final operation, the flow cannot be reused or any intermediate operation can be used, otherwise an exception will be thrown:

java.lang.IllegalStateException: stream has already been operated upon or closed

As the saying goes, “You will never step into the same river twice.” That’s exactly what it means.

Commonly used final operations are as follows:


Stream provides the method’forEach’to iterate over each data in the stream. The following code snippet uses forEach to output 10 random numbers:

Random random = new Random();


Count is used to count the number of elements in the stream.

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");


Collect is a reduction operation, which can accept various practices as parameters and accumulate elements in the flow into a summary result.

List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
strings  = -> string.startsWith("Hollis")).collect(Collectors.toList());
//Hollis, HollisChuang, Hollis666, Hollis

Next, let’s use a graph to illustrate how different final operations can be used for a Stream in the preceding example when it is processed successively by filter, map, sort, limit and distinct.

The following figure shows the locations, inputs, outputs of all the operations described in this paper, as well as the results of a case study.  


This paper introduces the use and advantages of Stream in Java 8. Several uses of Stream are also accepted, namely Stream creation, intermediate operation and final operation.

There are two ways to create Stream, one is through the stream method of the collection class, the other is through the stream method of Stream.

Stream’s intermediate operation can be used to process Stream. The input and output of the intermediate operation are Stream. The intermediate operation can be filtering, conversion, sorting, etc.

The final operation of Stream can convert Stream into other forms, such as calculating the number of elements in the stream, changing the stream into a set, and traversing elements.

Author: Hollischuang

Read the original text

This article is the original content of Yunqi Community, which can not be reproduced without permission.