Collections is the most used API in Java, and almost every programmer says hello to it every day. It allows you to combine the same, similar, and related data together for easy use, extraction, and computation. In real Java applications, the use of collections tends to become more complex with business requirements, complexity, and may involve more operations such as summing, averaging, grouping, filtering, sorting, and so on. How do these operations mix and how do they happen? Iterate, iterate, calculate? Performance aside, these actions have seriously affected the cleanliness of code that few people want to read.
So, is there any good way to solve this situation? After all, collections are the most common operation. Didn’t the designers of the Java language realize this? How can you save valuable time and make your programmer’s life a little easier?
As you may have guessed, the answer is “Stream”.
This article will start with the Stream API in JDK1.8 to give you a sense of how easy collection operations can be.
What is Stream
Stream is a new member of the Java API that allows you to process a collection of data declaratively (expressed through a query statement, rather than writing an implementation AD hoc). You can think of it as a high-level iterator that traverses a collection of data. In addition, streams can be transparently parallelized without having to write any multithreaded code.
Let’s start with a quick comparison of the benefits of using Stream. The following two pieces of code filter people whose names contain the “XC” string and sort them by age.
Traditional way (before JDK1.8, non-stream) :
List<People> peoples = new ArrayList<>(); For (People: allPeoples) {if (people.getName(). Contains ("xc")) {peoples. Add (People); }} // Collections.sort(peoples, new Comparator<People>() {@override public int compare(People p1, People p2) { return Integer.compare(p1.getAge(), p2.getAge()); }});Copy the code
The Stream mode:
List<People> peoples2 = allPeoples.stream()
.filter(people -> people.getName().contains("xc"))
.sorted(Comparator.comparing(People::getAge))
.collect(Collectors.toList());
Copy the code
No contrast, no harm, the effect is obvious. From a development perspective, the Stream approach has the following obvious benefits:
-
The code is written declaratively: stating what you want to accomplish (filter out data that meets the criteria) rather than how to implement an operation (control flow statements such as loops and if conditions).
-
Link multiple basic operations: Link multiple basic operations together to express the complex data processing pipeline (figure below), while showing clear and readable code.
The Stream API is very powerful. Similar to the Stream processing pipeline method above, there are many application scenarios. Theoretically, it can generate a pipeline with infinite length. More importantly, in complex businesses you don’t have to worry about threads and locks to make certain data processing tasks parallel. The Stream API does it for you!
Stream
“Flow”, a declaratively sequential or parallel pipeline of operations on each element in the set by transforming the set into a sequence of elements called a flow.
In other words, all you have to do is tell the flow what you want, and the flow will do whatever it wants behind the scenes, and you’ll just “ride the wave.”
The Stream operation
The entire flow operation is a pipeline on which elements are processed one by one, as shown in the figure below.
Stream
is a Stream of type Stream
. The Stream is converted to a Stream of type Stream
by a series of operations, such as filtering reserved elements, sorting elements, type conversion, etc. Finally, a termination operation is performed to convert Stream back to the collection type. You can also directly process the elements, such as printing, counting totals, calculating maximum values, and so on.
Many Stream operations return a Stream themselves, so multiple operations can be directly concatenated, as in the Stream code in the example above.
In the old days, you would have done an iterator or foreach loop and walked through it, doing it step by step. But if you use a stream, you can give instructions declaratively, and the stream will do it for you.
Through the Stream pipeline and examples above, the Stream operations are roughly divided into two types: intermediate operators and termination operators.
1. Intermediate operators
In the case of data flows, the data flow can still be passed to the lower-level operator after the intermediate operator executes the specified processing logic.
There are eight intermediate operators:
-
Map (mapToInt, mapToLong, mapToDouble) conversion operator: for example, A->B, which provides the default int, long, double operators.
-
Flatmap (flatmapToInt flatmapToLong, flatmapToDouble) pat down operation: Int []{2,3,4} = 2,3,4; int[]{2,3,4} = 2,3,4;
-
Limit Operation: For example, if there are 10 data streams, I can use only the first three.
-
Distinct deduplication operation: Deduplication of duplicate elements.
-
Filter Filter operation: Filters the set data.
-
Peek consumption operation: If you want to perform some operations on the data, such as reading, editing and modification, etc.
-
Skip Action: Skip some elements.
-
Sorted operation: Sorts elements, provided that the Comparable interface is implemented, and of course you can customize the comparator.
(Refer to java.util.stream.stream for details)
2. Terminate the operator
The data goes through a series of intermediate operations, and the termination operator comes into play. Terminating operators are used to collect or consume data. Data does not flow down after terminating operations. Terminating operators can only be used once.
-
Collect: Collect all data. This operation is very important. The official Collectors provide many Collectors, and the core of the Stream is Collectors.
-
Count Indicates the final number of data collected.
-
FindFirst, findAny search operations: find the first one, findAny one, return type Optional.
-
NoneMatch, allMatch, anyMatch: Indicates whether a matching element exists in the data flow. The returned value is bool.
-
Min and Max Max operations: A custom comparator is required to return the maximum and minimum values in the data stream.
-
Reduce protocol operation: The value of the entire data flow is reduced to a value. Count, min, and Max use Reduce.
-
ForEach, forEachOrdered traversal: This is where the final data is consumed.
-
ToArray array operation: Converts elements of a data stream to arrays.
Said so much, echocardiography than action, as the saying goes: practice out of truth. So, let’s do it.
Iii. Actual combat exercise
A series of operations on Stream that must use the termination operator otherwise the entire Stream will not be executed.
1. map
Transformation, mapping operations to convert elements to other forms or extract information.
For example, get the ages of all People from the People set:
allPeoples.stream()
.map(People::getAge)
.forEach(System.out::println);
Copy the code
2. flatmap
Flatten the elements, reassemble the flattened elements into streams, and serially merge the streams into a single Stream.
For example, split a string with a – character and print:
Stream.of("x-c-b-e-y-o-n-d","a-b-c-d")
.flatMap(m -> Stream.of(m.split("-")))
.forEach(System.out::println);
Copy the code
Output:
x
c
b
e
y
o
n
d
a
b
c
d
Copy the code
3. limit
Limiting the number of elements in a collection.
For example, if there are 10 elements in the set, I can use only the first four:
,2,3,4,5,6,7,8,9,10 Stream of (1) limit (4) the forEach (System. Out: : println);Copy the code
Output:
1
2
3
4
Copy the code
4. distinct
Deduplication operation, deduplication of duplicate elements, similar to the keyword distinct in the database.
For example, there may be some duplicate data in the collection that needs to be de-duplicated:
Stream.of("xcbeyond","Niki","Liky","xcbeyond")
.distinct()
.forEach(System.out::println);
Copy the code
Output:
xcbeyond
Niki
Liky
Copy the code
5. filter
Filter, filter, filter certain elements, do not meet the filtering conditions will not enter the downstream stream.
For example, filter out all the even numbers in a set of numbers:
,2,3,4,5,6,7,8,9,10 Stream of (1). The filter (n - > 0 = = n % 2). The forEach (System. Out: : println);Copy the code
Output:
2, 4, 6, 8, 10Copy the code
6. peek
Consumption operation, if you want to perform some operations on data, such as: read, edit, modify, etc.
For example, change the name of the People set to name+age:
allPeoples.stream()
.peek(people -> people.setName(people.getName() + people.getAge()))
.forEach(people -> System.out.println(people.getName()));
Copy the code
7. skip
Skip operations, skip some elements.
For example, for a set of numbers, skip the first four elements:
,2,3,4,5,6,7,8,9,10 Stream of (1). Skip (4) the forEach (System. Out: : println);Copy the code
Output:
5, 6, 7, 8, 9, 10Copy the code
8. sorted
The sort operation, which sorts elements if the Comparable interface is implemented, can also be used to customize the comparator.
For example, sort the People set by age:
allPeoples.stream()
.sorted(Comparator.comparing(People::getAge))
.forEach(System.out::println);
Copy the code
9. collect
The collection operation, the termination operator, is used to collect the final data into a new collection, such as a List, Set, Map, etc.
For example, sort the People set by age and put it in a new set for future use:
List<People> sortedPeople = allPeoples.stream()
.sorted(Comparator.comparing(People::getAge))
.collect(Collectors.toList());
Copy the code
10. count
The count operation, used to count the number of elements in a collection, returns type long.
For example, count the number of People older than 30:
long count = allPeoples.stream()
.filter(people -> people.getAge() > 30)
.count();
Copy the code
11. FindFirst, findAny
Find operation, find the first one, any one, and return type Optional. It is often used to query the elements in a set that meet the criteria, and to determine with option.isPresent () to prevent the exception of forcibly obtaining data elements when they are not found.
For example, find the person named xcbeyond in the People set:
People xcbeyondPeople = null;
Optional<People> optional = allPeoples.stream()
.filter(people -> "xcbeyond".equals(people.getName()))
.findFirst();
if (optional.isPresent()) {
xcbeyondPeople = optional.get();
}
Copy the code
12. NoneMatch, allMatch, anyMatch
The matching operation checks whether there are elements in the data stream that match the condition and returns a Boolean value.
-
NoneMatch: Element with no matching condition
-
AllMatch, anyMatch: all matches
For example, to determine if there is a person named xcBeyond in the People set:
boolean bool = allPeoples.stream()
.allMatch(people -> "xcbeyond".equals(people.getName()));
Copy the code
13. Min and Max
The maximum-value operation returns the largest and smallest element in the data stream, based on a custom comparator.
For example, find the oldest person in the People set:
People maxAgePeople = null; Optional<People> maxAgeOptional = allPeoples.stream() .max(Comparator.comparing(People::getAge)); If (maxageOption.isPresent ()) {maxAgePeople = maxageoption.get (); }Copy the code
14. reduce
Specification operation: the value of the entire data stream is specified as a value, in which count, min, and Max are reduced.
For example, summing a set of integers:
,9,8,4,5,6 int sum = Stream of (1, 1). The reduce (0, (e1 and e2) - > e1 + e2); System.out.println(sum);Copy the code
Output:
32
Copy the code
Four,
This article gives you a thorough introduction to the basic aspects of using Stream, and shows you how to use each operator. Once you have mastered these basic operations, you will know how to use them when you need to use them in complex processing logic. This also gives you a step up with collections, which saves you a lot of trouble. More in-depth explanation of Stream, such as parallel processing and whether it is efficient, will be elaborated and verified in detail in the following chapters to eliminate doubts and concerns in use.
The next article will cover the ultimate operation, Collectors, which allows collections to do complex grouping, joins, and aggregation, much like SQL does.