This blog post shows you how to use ** Java 8 ** to ** Stream API ** to process data declaratively.
In Java, collections and arrays are two common data structures that perform many operations on a regular basis, such as add, delete, modify, query, aggregate, statistics, and filter. These operations also exist in relational databases. However, in Java 8 and earlier, working with collections and arrays is not very convenient.
This issue has been greatly mitigated in Java 8 by introducing a new abstraction called the Stream API, which allows you to process data in a declarative way. This article will show you how to use Stream. Keep in mind that stream performance and principles are not central to this article.
Streams provide a high-level abstraction of Java collection operations and expressions by querying data from the database, similar to SQL statements.
The Stream API greatly increases the productivity of Java programmers and allows them to write effective, clean and concise code.
The set of elements to process is considered a stream transmitted in the pipeline. These elements can be processed by nodes in the pipeline, such as filters, sorts, and aggregates.
--There is no storage. Streams are just views of the data source, not data structures. --Streams are functional in nature. Changes to the stream do not change the data source. For example, filtering a stream does not remove the filtered elements, but produces a new stream that does not contain the filtered elements. --Lazy evaluation. Operations on the stream are not performed immediately. Only executed when the user really needs the result. --Can be consumed. The elements of the stream are visited only once during the life of the stream. Once traversed, the stream is invalid, like a container iterator. If you want to traverse the Stream again, you need to regenerate the new Stream. Let's use an example to see what Stream can do.
In the previous example, we take some plastic balls as a data source, filter the red balls and melt them into random triangles. Another filter removes small triangles. The damping agent sums the circumferences.
As shown in the figure above, a stream contains three important operations: stream creation, intermediate operation, and terminal operation.
In Java 8, you can use many methods to create a Stream.
In Java 8, in addition to many stream-related classes, the collection class itself has been enhanced. The Java 8 Stream method can convert a collection to a Stream.
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis"); Stream<String> stream = strings.stream();
In the example above, we are creating a stream from an existing list. The parallelStream method can also create a parallel stream for a collection.
You also often create Streams from collections.
The of method provided by Stream can be used to directly return a Stream consisting of the specified elements.
Stream<String> stream = Stream.of("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis");
The code above uses the of method to create a stream and return it.
Streams have many intermediate operations that can be combined to form a pipeline. Each intermediate operation is like a worker on the pipeline. Each worker can process a Stream. The intermediate operation returns a new Stream.
Below is a list of common intermediate operations.
filter The filter method is used to filter the elements by the specified criteria. The following code snippet uses the filter method to filter an empty string.
List<String> strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis"); strings.stream().filter(string -> ! string.isEmpty()).forEach(System.out::println); //Hollis, , HollisChuang, H, hollis
map The map method maps each element to the corresponding result. The code snippet below uses the map method to generate the square number of the corresponding element.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); numbers.stream().map( i -> i*i).forEach(System.out::println); //9,4,4,9,49,9,25
limit/skip Limit returns the first N elements of the Stream. Skip discards the first N elements of Stream. The following code snippet uses the limit method to hold the first four elements.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); numbers.stream().limit(4).forEach(System.out::println); //3,2,2,3
sorted The sorted method sorts the elements of the Stream. The following code snippet uses the sorted method to sort the elements of a Stream.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); numbers.stream().sorted().forEach(System.out::println); //2,2,3,3,3,5,7
distinct To remove duplicates, use the distinct method. The following code snippet uses the distinct method to deduplication the element.
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); numbers.stream().distinct().forEach(System.out::println); //3,2,7,5
Next, I will explain what happens to Stream after performing filter, map, sort, limit, and distinct operations using examples and diagrams.
The code is shown below.
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hello", "HelloWorld", "Hollis"); Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3) .distinct();
The following figure shows each step and its results.
The terminal operation of the stream also returns Stream. How do I convert a stream to the desired type? For example, count the elements in a Stream and convert that Stream to a collection. Terminal operation is required to do this.
Terminal operations consume Stream and produce the final result. That is, after a terminal operation has been performed on a stream, that stream cannot be reused and no intermediate operations are allowed on that stream. Otherwise an exception will be thrown.
java.lang.IllegalStateException: stream has already been operated upon or closed
This is the same as the saying, "You can't step into the same river twice."
The table below shows common terminal operations.
forEach The forEach method iterates over the elements in the stream. The following code snippet uses forEach to return 10 random numbers.
Random random = new Random(); random.ints().limit(10).forEach(System.out::println);
count The count method counts the elements in the Stream.
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis"); System.out.println(strings.stream().count()); //7
collect A collect operation is a reduce operation that can accept various parameters and accumulate stream elements in the summary result.
List<String> strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis"); strings = strings.stream().filter(string -> string.startsWith("Hollis")).collect(Collectors.toList()); System.out.println(strings); //Hollis, HollisChuang, Hollis666, Hollis
Next, we will continue to use filters, maps, sorts, limits, and diagrams showing that separate operations have been performed to show the results of different terminal operations on the Stream given in the above example.
The following figure uses an example to show the inputs and outputs of all the operations described in this article.
This article describes how to use streams and their features in Java 8. This article also covers creating streams, intermediate stream operations, and terminal operations.
There are two ways to create a stream, one is to use the stream method of the collection, and the other is to use the stream's of method.
Stream intermediate operations can process streams. Both the input and output of the intermediate operation are Streams. Intermediate operations include filters, maps, and sorts.
Stream intermediate operations can convert a stream to some other container, such as counting the elements in the stream, converting the stream to a collection, or iterating over the elements in the stream.