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

Your attention is the biggest motivation for me to continue to publish