A small article with examples of using the Stream API in Java8 , which I hope will help novice users to master and use the functionality.
Often, the
Stream API in Java8 is used to work with collections, allowing you to write code in a functional style.
Convenience and simplicity of methods contribute to the interest in this functionality among developers since its release.
So, what is the Stream API in Java8? "Package java.util.stream" - "Classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections" . I will try to give my own version of the translation, in fact, this is support for the functional style of operations on streams, such as processing and “folding” of the processed data.
“Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a collection, an array, a generator function, or an I / O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce ” - description from the site .
Let's try to understand this definition. The authors tell us about the presence of intermediate and final operations, which are combined in the form of conveyors. Flow conveyors contain a source (for example, collections, etc.) followed by intermediate and final operations and examples are given. It is worth noting here that all intermediate operations on streams are lazy (LAZY). They will not be executed until the terminal (final) operation is called.
Another interesting feature is the presence of parallelStream () . I use these capabilities to improve performance when processing large amounts of data. Parallel threads will speed up the execution of certain types of operations. I use this opportunity when I know that the collection is large enough to handle it in the “ForkJoin” version. Read more about ForkJoin in the previous article on this topic - “Java 8 in parallel. We learn to create subtasks and monitor their implementation. "
We finish with the theoretical part and proceed to simple examples.
The example shows finding the maximum and minimum values ​​from the collection.
ArrayList<Integer> testValues = new ArrayList(); testValues.add(0,15); testValues.add(1,1); testValues.add(2,2); testValues.add(3,100); testValues.add(4,50); Optional<Integer> maxValue = testValues.stream().max(Integer::compareTo); System.out.println("MaxValue="+maxValue); Optional<Integer> minValue = testValues.stream().min(Integer::compareTo); System.out.println("MinValue="+minValue);
Let's slightly complicate the example and add exceptions (in the form of null) at the maximum value in example 2.
ArrayList<Integer> testValuesNull = new ArrayList(); testValuesNull.add(0,null); testValuesNull.add(1,1); testValuesNull.add(2,2); testValuesNull.add(3,70); testValuesNull.add(4,50); Optional<Integer> maxValueNotNull = testValuesNull.stream().filter((p) -> p != null).max(Integer::compareTo); System.out.println("maxValueNotNull="+maxValueNotNull);
Let's complicate the examples. Create a collection of "sports camp", consisting of the fields "Name" and "Number of days in a sports camp." The very example of creating a class below.
public class SportsCamp { private String name;
And now examples of working with new data:
import java.util.Arrays; import java.util.Collection; public class Start { public static void main(String[] args) { Collection<SportsCamp> sport = Arrays.asList( new SportsCamp("Ivan", 5), new SportsCamp("Petr", 7), new SportsCamp("Ira", 10) ); String name = sport.stream().max((p1,p2) -> p1.getDay().compareTo(p2.getDay())).get().getName(); System.out.println("Name="+name); } }
In the example, the name was found, Irina, who will be in the camp the longest.
Let's transform an example and create a situation when we have an error crept in, and one of the null entries in the name.
Collection<SportsCamp> sport = Arrays.asList( new SportsCamp("Ivan", 5), new SportsCamp( null, 15), new SportsCamp("Petr", 7), new SportsCamp("Ira", 10) );
In this case, you will get a result equal to "Name = null". Agree that we did not want this. We will slightly change the search in the collection to a new version.
String nameTest = sport.stream().filter((p) -> p.getName() != null).max((p1, p2) -> p1.getDay().compareTo(p2.getDay())).get().getName();
The result, "Ira" - is true.
The examples show us how to find the minimum and maximum values ​​for collections with small additions as an exception to null values.
As we said, the available methods can be divided into two large groups, intermediate operations and final ones. Authors may call them differently; for example, the variant names of conveyor and terminal methods are used in literature and articles. When working with methods, there is one design feature, you can “throw in” a lot of intermediate operations, at the end making a call to one terminal method.
In the new example, we will add sorting and output of a certain element, for example, add a filter by the names with the encountered “Ivan” and calculate these elements (excluding null values).
long countName = sport.stream().filter((p) -> p.getName() != null && p.getName().equals("Ivan")).count(); System.out.println("countName="+countName);
Adding new SportsCamp ("Ivan", 17) to the collection, we get the result equal to "countName = 2". Found two entries.
In these examples, creating a stream from the collection was used, other options are available, for example, creating a stream from the required values, for example, Stream streamFromValues ​​= Stream.of ("test1", "test2", "test3"), other options are possible.
As mentioned above, users have the opportunity to use "processing" using parallelStream ().
Slightly changing the example, we get a new version of the implementation:
long countNameParallel = sport.parallelStream().filter((p) -> p.getName() != null && p.getName().equals("Ivan")).count(); System.out.println("countNameParallel=" + countNameParallel);
The peculiarity of this option is the implementation of parallel stream. I would like to note that parallelStream () is justifiably used on powerful servers (multicore) for large collections. I do not give a clear definition and exact size of collections, because a lot of parameters need to be identified and calculated. Often, only testing can show you an increase in performance.
We got a little acquainted with simple operations, understood the difference between pipeline and terminal operations, tried both. And now let's look at examples of more complex operations, for example, collect and Map, Flat and Reduce.
Once again, look at the official documentation documentation and try to implement their examples.
In the new example we will try to convert one collection to another, with names starting with “I” and write it in the List.
List<SportsCamp> onlyI = sport.stream().filter(p -> p.getName() != null && p.getName().startsWith("I")).collect(Collectors.toList()); System.out.println("SIZE="+onlyI.size());
The result will be three. It should be noted here that the order of specifying the exclusion of null elements is significant.
Please note that Collectors has a ton of features, including the derivation of a mean value or information with statistics. As an example, let's try to connect the data, like this:
String campPeople = sport.stream().filter(p -> p.getName() != null).map(SportsCamp::getName).collect(Collectors.joining(" and ","In camp "," rest all days.")); System.out.println(campPeople);
Result: "In camp Ivan and Petr and Ivan rest all days". There are several options for using Collectors.joining.
From Map, Flat, and Reduce, consider the example with reduce. Map and flat-map will be discussed in the following articles.
Reduce is used to “assemble” elements, in simple language, if you want to create a new instance of an object with aggregating indicators of other elements in the stream, then reduce will work for you. There are several uses. Consider one of them, for example, let's sum up the data for all days in a sports camp.
Integer daySum = sport.stream().reduce(0, (sum, p) -> sum += p.getDay(), (sum1, sum2) -> sum1 + sum2); System.out.println("DaySize=" + daySum);
In this version, reduce takes three values, the first - the identifier, the second - the battery, and the third is actually a “merge”. There are several other options.
The article describes only a small part of the methods, perhaps they will interest many and appear in new projects. In the aggregate, the lambda functional becomes an excellent tool for writing short and fast-executable code. Good luck to all.