In the last article I covered the basics of a Stream Stream and its basic method usage, and in this article we will continue to cover the other operations of a Stream
For those of you who haven’t read the previous article, click here to learn how to work with collections quickly and succintly — Java8 Stream
It’s worth noting that you must learn about lambda before you can learn about Stream. This article also assumes that the reader already has knowledge of lambda.
The main content of this article:
- A specialized form of flow — numerical flow
- Optional class
- How do YOU build a flow
- Collect method
- Parallel flow-related issues
I. Numerical flow
Int sum = list.stream().map(Person::getAge).reduce(0, Integer::sum); The method of calculating the sum of elements implies the packing cost. The map(Person::getAge) method then flows into the Stream type, and each Integer needs to be unboxed into an original type and then summed by the sum method, which greatly affects the efficiency.
Java 8 addresses this problem with the conscientious introduction of IntStream, DoubleStream, and LongStream, whose elements are primitive data types: int, double, and Long
1. Conversion between flow and numerical flow
The stream is converted to a numerical stream
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);
Copy the code
Of course this would be an error
LongStream longStream = list.stream().mapToInt(Person::getAge);
Copy the code
Because getAge returns an int (an Integer can also be converted to an IntStream)
A numerical stream is converted to a stream
It’s easy, just a boxed
Stream<Integer> stream = intStream.boxed();
Copy the code
2. Numerical flow method
The following methods work without saying much, just look at the name:
- sum()
- max()
- min()
- Business (such as)…
3. Value range
IntStream and LongStream have range and rangeClosed methods for numeric range processing
- IntStream: rangeClosed(int, int)/range(int, int)
- LongStream: rangeClosed(long, long)/range(long, long)
The difference between these two methods is that one is a closed interval and the other is a half-open and half-closed interval:
- RangeClosed (1, 100) : [1, 100]
- Range (1, 100) : [1, 100]
We can use intstream.rangeclosed (1, 100) to generate streams from 1 to 100
RangeClosed (1, 10); IntStream = intstream. rangeClosed(1, 10); int sum = intStream.sum();Copy the code
2. Optional classes
NullPointerException is a word that every Java programmer hates to see. To address this problem, Java 8 introduced a new Optional container class that can represent the presence or absence of a value, instead of returning the problematic NULL. This class is often present in the code in the previous article and is an improvement on this problem.
The Optional class uses the following methods:
- IsPresent () : returns true if the value exists, flase otherwise
- Get () : Returns the current value, or throws an exception if it does not exist
- OrElse (T) : Returns the value if it exists, or T otherwise
The Optional class also has three specialized versions: OptionalInt, OptionalLong, and OptionalDouble, which is the type that the Max method in the value stream returns
The Optional class actually has a lot of knowledge, explain it may also open an article, here first cover so much, first know how to use the basic can.
Build flow
Earlier we had a stream that was transformed from an original data source, but we could have built the stream directly.
1. Value creation flow
- Stream.of(T…) : stream.of (“aa”, “bb”) generates a Stream
Generate a String Stream<String> Stream = stream.of ("aaa"."bbb"."ccc");
Copy the code
- Stream.empty() : Generates an empty Stream
2. Array creation flow
Create a stream based on the array type of the argument:
- Arrays.stream(T[ ])
- Arrays.stream(int[ ])
- Arrays.stream(double[ ])
- Arrays.stream(long[ ])
Array.stream (T[], int, int)
Int [] a = {1, 2, 3, 4}; Arrays.stream(a, 1, 3).forEach(System.out :: println); Print 2, 3Copy the code
3. File generation flow
Stream<String> stream = Files.lines(Paths.get("data.txt"));
Copy the code
Each element is one of the rows in a given file
4. Functions generate streams
Two methods:
- Iterate: Applies the function to each newly generated value in turn
- Generate: Accepts a function and generates a new value
Iterate (0, n -> n + 2) Stream. Iterate (0, n -> n + 2) Stream. Generate (() -> 1) generate Stream, all elements are 1Copy the code
Collect data
The Coollect method, acting as a terminal operation, accepts a Collector interface parameter and can perform some aggregation of data
1. Collect
The most common method is to collect all elements of a stream into a List, Set, or Collection
- toList
- toSet
- toCollection
- toMap
List newlist = list.stream.collect(toList());
Copy the code
Map<Integer, Person> Map = list.stream().collect(Person::getAge, p -> p));Copy the code
2. A summary
(1) the counting
Used to calculate the sum:
long l = list.stream().collect(counting());
Copy the code
Yes, as you can imagine, the following could also work:
long l = list.stream().count();
Copy the code
Recommend the second option
(2) summingInt, summingLong, summingDouble
Summing, yes, also computes the sum, but requires a function argument
Calculate Person age summation:
int sum = list.stream().collect(summingInt(Person::getAge));
Copy the code
Of course, this can also be simplified as:
int sum = list.stream().mapToInt(Person::getAge).sum();
Copy the code
In addition to the above two, it can also be:
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
Copy the code
Recommend the second option
As you can see, functional programming often provides multiple ways to do the same thing
(3) averagingInt, averagingLong, averagingDouble
You can tell by the name. Take the average
Double average = list.stream().collect(averagingInt(Person::getAge));
Copy the code
Or you could write it this way
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
Copy the code
Note, however, that the two return values are of different types
SummarizingInt, summarizingLong, summarizingDouble
These three methods are special. For example, summarizingInt returns IntSummaryStatistics
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
Copy the code
IntSummaryStatistics contains the calculated average, total, summation, and maximum, which can be obtained using the following methods
3. Get the most value
The maxBy and minBy methods require a Comparator interface as a parameter
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
Copy the code
We can also get the same result directly using the Max method
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
Copy the code
4. Joining string
A more common method of concatenating string elements in a stream, the underlying implementation uses a StringBuilder for concatenating strings
String s = list.stream().map(Person::getName).collect(joining()); Results: jackmiketomCopy the code
String s = list.stream().map(Person::getName).collect(joining(",")); Results: jack, mike, TomCopy the code
Joining has another unique override:
String s = list.stream().map(Person::getName).collect(joining(" and "."Today "." play games.")); Result: Today Jack and Mike and Tom play games.Copy the code
“Today” opens the head, “play games.” puts the end, and concatenates the strings in the middle
5. GroupingBy grouping
GroupingBy is used to group data, eventually returning a Map type
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));
Copy the code
In this example we are grouping by age, and each Person object of the same age is grouped into a group
You can also see that Person::getAge determines the Map key (Integer type) and list type determines the Map value (list type).
Multistage grouping
GroupingBy can accept a second parameter to implement multi-level grouping:
Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupingBy(...) ));Copy the code
The returned Map key is of the Integer type and the value is of the Map<T, List> type, that is, groupBy(…). Type returned
Collect data by group
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));
Copy the code
In this example, we group by age, then summingInt(Person::getAge) calculates the sum of the ages of each group separately (Integer), and returns a Map
According to this method, we can know that what we wrote earlier is:
groupingBy(Person::getAge)
Copy the code
In fact, it is equivalent to:
groupingBy(Person::getAge, toList())
Copy the code
6. PartitioningBy partition
The difference between partitioning and grouping is that partitions are divided according to true and false, so the lambda accepted by partitioningBy is also T -> Boolean
List<Person>> Map = list.stream().collect(partitioningBy(p -> p.gage () <= 20)); Printout {false=[Person{name='mike', age=25}, Person{name='tom', age=30}],
true=[Person{name='jack', age=20}]
}
Copy the code
Similarly, partitioningBy can be partitioned by adding a collector as a second parameter, doing multiple partitions like groupBy and so on.
Parallel five.
List.stream () converts the list type to a stream type, and list.parallelstream () converts it to a parallelStream. So you can usually use parallelStream instead of the stream method
A parallel stream is one that splits content into chunks of data and uses different threads to process each chunk separately. This is also a feature of streams, because prior to Java 7, parallel processing of data sets was cumbersome; you had to split the data yourself, allocate threads yourself, and ensure synchronization to avoid contention if necessary.
Stream makes it easier for programmers to parallelize collections of data, but be aware that not all situations are appropriate. Sometimes parallelization is even less efficient than sequential processing, and sometimes it can lead to data processing errors due to thread safety issues, as I’ll explain in the next article.
Take the following example
int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);
Copy the code
We calculate the sum of all numbers from 1 to 100 using one line of code. Parallel is used for parallelism.
But in fact, such a calculation, efficiency is very low, than the use of parallelism is also low! Partly because of the boxing problem, which was mentioned earlier and will not be discussed again, and partly because the Iterate method makes it difficult to break up the numbers into separate blocks and execute them in parallel, thus reducing efficiency.
Decomposability of a stream
This brings us to the decomposability of streams. When using parallelism, we need to pay attention to whether the data structure behind the stream is easily decomposable. The well-known ArrayList and LinkedList, for example, are clearly superior in decomposition.
Let’s look at the decomposability of some data sources
The data source | decomposability |
---|---|
ArrayList | excellent |
LinkedList | poor |
IntStream.range | excellent |
Stream.iterate | poor |
HashSet | good |
TreeSet | good |
sequential
In addition to decomposability, and the packing problem just mentioned, it is also worth noting that some operations themselves perform worse on parallel streams than on sequential streams, such as: Limit, findFirst, because these two methods take into account orderliness of elements, parallelism itself is against orderliness, and because of this findAny is generally more efficient than findFirst.
The efficiency of six.
Finally, efficiency. Many of you have probably heard about Stream inefficiency. In fact, for some simple operations, such as simply traversing, finding the best value, and so on, Stream performance can be lower than traditional loop or iterator implementations, or even much lower.
But for complex operations, such as complex object reduction, Stream performance is comparable to manual implementation, and in some cases it can be far more efficient using parallel streams. Good steel used in the blade, in the right scene to use, to play its greatest use.
The appearance of functional interface is mainly to improve the efficiency of code development and enhance the readability of code. At the same time, very high performance is not always required in real development, so Stream and lambda make a lot of sense.
reading
- Working with collections quickly and succinctly — Java8 Stream (top)
Guess you like
- You have to figure out String, StringBuilder, StringBuffer
- Share some personal nuggets from the Java back end
- Shiro + SpringBoot integration JWT
- Teach Shiro to integrate SpringBoot and avoid various potholes