Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

What is a Stream

1.1 introduction

A new feature has been added to java8: Stream. A Stream, unlike an I/O Stream, is more like a collection class with Iterable, but behaves differently. It is an enhancement to a collection object, allowing developers to work declaratively with data sources (collections, arrays, and so on). It focuses on various efficient aggregate operations and bulk data operations on data sources.

For example, a Stream implicitly iterates internally and converts data if it is given something to do with the elements it contains, such as “filter out strings greater than 10” or “get the first letter of each string.”

Stream is a high-level abstraction of Java collection operations and expressions. The Stream API treats the data source it processes as a Stream, which is transmitted and computed in a Pipeline. The supported operations include filtering, sorting, aggregation, etc., and the final processing result is obtained when it reaches the end point.

A few key concepts:

  1. The Stream element is a queue of elements from the data source; the Stream itself does not store elements.

  2. Data sources (sources of streams) include collections, arrays, I/O channels, generators, and so on.

  3. The aggregation operations are similar to the filter, map, find, match, and sorted operations in SQL

  4. The Pipeline operation Stream returns the Stream object itself after the operation in the Pipeline, so that multiple operations are concatenated into a Pipeline and form Fluent style code. This approach optimizes operations, such as delayed execution and short-circuiting.

  5. The Stream API implements internal iteration using a Visitor pattern, unlike the previous java8 iteration of collections (external iteration).

  6. The Stream API supports both serial (Stream()) and parallel (parallelStream()) operations.

1.2 Stream API features:

  1. The use of the Stream API goes hand-in-hand with lambda expressions, also a new java8 feature, to greatly improve coding efficiency and code readability.

  2. The Stream API provides serial and parallel operations. The parallel operation can take advantage of the multi-core processor, and the parallel operation can be performed by fork/join to improve the running speed.

  3. The Stream API works in parallel to write efficient concurrent programs without writing multithreaded code, and generally avoids multithreaded code errors.

Unlike the previous Collection operation, the Stream operation has two basic characteristics:

  • Pipelining: All intermediate operations return the flow object itself. These operations can be cascaded into a pipe, as in fluent style. This allows you to optimize operations, such as delay and short-circuiting.
  • Internal iteration: Previously, collections were iterated explicitly outside the collection using either Iterator or for-each. This is called external iteration. A Stream provides a means of iterating internally through a Visitor pattern.

Why Stream? Experience the convenience of functional programming! Reduce your code!

**Stream operations are based on a combination of functional interfaces, which in Java have only one exposed method in the interface. **Java8 provides many functional interfaces, usually declared with the @functionalinterface annotation. Some functional interfaces are necessary.

Create a Stream

Class diagram of stream:

2.1 Through Collection interface functions

The Collection interface in Java8 is extended to provide two methods for retrieving streams:

  1. Default Stream< E> Stream () : Returns a sequential Stream
List<String> strings = Arrays.asList("test"."lucas"."Hello"."HelloWorld"."Go Wuhan!");
Stream<String> stream = strings.stream();
Copy the code
  1. Default Stream< E> parallelStream() : returns a parallelStream
 List<Employee> list = new ArrayList<>();
 Stream<Employee> stream = list.stream();
 Stream<Employee> parallelStream = list.parallelStream();
Copy the code

Parallel flow is a multi-threaded mode, which needs to consider thread safety.

2.2 by the Stream

2.2.1 Through Stream: Creates a Stream by value

Stream<String> stream = Stream.of("test"."lucas"."Hello"."HelloWorld"."Go Wuhan!");
Copy the code

2.2.2 Create a Stream through the Stream function

You can use the Stream static methods stream.iterate () and stream.generate () to create an infinite Stream.

  1. Iterate: public static< T> Stream< T> iterate(final T seed, final UnaryOperator< T> f)
  2. Public static< T> Stream< T> generate(Supplier< T> s)

Example:

    / / iteration
    Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(2);
    stream3.forEach(System.out::println);
 
    System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- --");
 
    / / generated
    Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
        @Override
        public Double get(a) {
            returnjava.lang.Math.random(); }}); Stream<Double> generateB = Stream.generate(()-> java.lang.Math.random()); Stream<Double> generateC = Stream.generate(java.lang.Math::random).limit(4);
    generateC.forEach(System.out::println);
    
// Execution result
0
2
-------------
0.0064617087705397536
0.24943325913078163
0.9396182936441738
0.031970039813425166
Copy the code

This method of function creation is less commonly used, but varies from scenario to scenario. The above example illustrates how important the combination of a Stream and a Lambda expression is. The code is therefore concise and easy to understand.

Common usage of intermediate operation of Stream

There are a lot of middle Stream operation, more intermediate operations can be connected to form a line, between each operation as one of the workers on the assembly line, each worker can be processed convection, after processing the result or a Stream, unless the assembly line to trigger termination of operations, or intermediate operation will not perform any processing! When the operation terminates, it is processed all at once, which is called lazy evaluation.

These intermediate operations are some of the Stream interface uses we often use:

3.1 the filter method

The filter method filters the elements by setting conditions and produces a new stream (that is, you select the elements in the filter stream).

For example, the following code snippet uses the filter method to filter out empty strings:

public static void main(String[] args) {
    List<String> strings = Arrays.asList("test"."lucas"."Hello"."HelloWorld"."Go Wuhan!"); strings.stream().filter(string -> ! string.isEmpty()).forEach(System.out::println); }Copy the code

The results

Test Lucas Hello worldCopy the code

3.2 concat method

The concat method concatenates two streams together to compose a single Stream. If both input streams are sorted, the new Stream is sorted. If either of the input streams is parallel, the new Stream is also parallel; When a new Stream is closed, both of the original input streams will be closed.

Example:

    Stream.concat(Stream.of(1.4.3), Stream.of(2.5))
            .forEach(integer -> System.out.print(integer + ""));
Copy the code

The results

1, 4, 3, 2, 5Copy the code

3.3 the map method

methods describe
map(Function f) Take as an argument a function that is applied to each element and mapped to a new element.
mapToDouble(ToDoubleFunction f) Take as an argument a function that is applied to each element, producing a new DoubleStream.
mapToInt(ToIntFunction f) Receives a function as an argument that is applied to each element, producing a new IntStream.
mapToLong(ToLongFunction f) Take as an argument a function that is applied to each element, producing a new LongStream.
flatMap(Function f) Take a function as an argument, replace every value in the stream with another stream, and then join all the streams into one stream

The map method is used to map each element to the corresponding result.

For example, the following code snippet prints the square of an element using a map:

    List<String> strings = Arrays.asList("test"."lucas"."Hello"."HelloWorld"."Go Wuhan!"); strings.stream() .filter(string -> ! string.isEmpty()) .map(String::length) .forEach(System.out::println);// Run the result
		4
		5
		5
		10
		4        
Copy the code

Take a look at the map source code:

Function is a new functional interface to java8, and map needs to pass in an implementation of Function. The direct use of Lambda expressions perfectly allows us to focus only on the map () parentheses and omit a lot of verbose interface implementation writing (such as String::length, “::”). Another Java 8 feature “reference methods” is the colon “::” used for method calls.

3.4 flatMap method

The flatMap method is similar to the Map method in that the map method creates a new Stream, and the flatMap () method replaces the elements of the original Stream with the converted Stream.

If the Stream produced by the conversion function is null, it should be replaced by an empty Stream.

FlatMap has three variations of the original type, namely flatMapToInt, flatMapToLong and flatMapToDouble.

Stream.of(1.2.3)
    .flatMap(integer -> Stream.of(integer * 10))
    .forEach(System.out::println);
    // 10,20,30
Copy the code

The expression passed to the flatMap takes a parameter of type Integer and, by multiplying the original element by 10 through the conversion function, generates a stream with only that element, which replaces the elements in the original stream.

Reduce method in 3.5 Stream

The Reduce method has three overloaded methods.

The first method

Accepts a lambada expression of type BinaryOperator. The general application is as follows

Optional<T> reduce(BinaryOperator<T> accumulator);
Copy the code

example

List<Integer> numList = Arrays.asList(1.2.3.4.5);
int result = numList.stream().reduce((a,b) -> a + b ).get();
System.out.println(result);
Copy the code

Second method

The only difference from the first method implementation is that when it is first executed, the expression’s first argument is not the first element of the stream, but is specified by the signature’s first argument identity.

T reduce(T identity, BinaryOperator<T> accumulator);
Copy the code

example

List<Integer> numList = Arrays.asList(1.2.3.4.5);
int result = numList.stream().reduce(0,(a,b) ->  a + b );
System.out.println(result);
Copy the code

In fact, the two implementations are almost different, the first one has only one more word than the first to define the initial value. In addition, since the stream is empty, the first implementation does not directly calculate the result of the method. Instead, it wraps the result in Optional. We can obtain a result of type Integer through its GET method, and Integer allows NULL. The second implementation, because it allows specifying an initial value, does not return null even if the stream is empty. When the stream is empty, reduce simply returns the initial value.

The third way

The use of the third method compared with the first two somewhat complex, because the former two implementations have a defect, the results must be the same and the types of elements in stream, such as the following code example, the stream of type int, then the calculation results must also for int, this led to the lack of flexibility, can’t even finish some tasks, We are summing over a series of ints, but the sum is too large for an int and must be upgraded to a long. This third signature is useful and does not tie the execution result to the type of the element in the stream.

<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
Copy the code

example

List<Integer> numList = Arrays.asList(1.2.3.4.5.6);
        ArrayList<String> result = numList.stream().reduce(new ArrayList<String>(), (a, b) -> {
            a.add("An int to a string." + Integer.toString(b));
            return a;
        }, (a, b) -> null);
        System.out.println(result);
//[int converts to string:1, int converts to string:2, int converts to string:3, int converts to string:4, int converts to string:5, int converts to string:6]
Copy the code

If you are using parallelStream Reduce for concurrent operations, in order to avoid contention, each Reduce thread will have a separate result. Combiner is used to combine the results of each thread to get the final result.

The BinaryOperator is intended for multithreading, and if you do not declare multithreading in the Stream, you will not use the subtask and will not call this method. In addition, when using the BinaryOperator in multiple threads, there are thread safety concerns.

3.6 method of peek

The peek method generates a new Stream that contains all the elements of the original Stream and provides an instance of a Consumer function. Each element of the new Stream is consumed before the given Consumer function is executed. See Consumer.

    Stream.of(1.2.3)
            .peek(integer -> System.out.println("Preferred consumption function in Peek: This is" + integer))
            .forEach(System.out::println);
Copy the code

Running results:

Priority consumption function in PEEK: This is the priority consumption function in peek 1 1: this is the priority consumption function in peek 2 2: this is the priority consumption function in peek 3 3Copy the code

3.7 limit/skip method

Limit returns the first n elements of the Stream;

Skip is throwing away the first n elements.

For example, the following code snippet factoring four elements using the limit method:

List<Integer> numbers = Arrays.asList(1.2.3.4.5);
numbers.stream().limit(4).forEach(System.out::println);
/ / 1, 2, 3, 4
Copy the code

3.8 sorted method

The sorted method is used to sort convection.

For example, the following code snippet sorts using the sorted method:

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
Copy the code

3.9 a distinct method

Distinct is mainly used to remove weights.

For example, the following code snippet uses distinct to de-duplicate elements:

List<Integer> numbers = Arrays.asList(3.3.7.3.5);
numbers.stream().distinct().forEach(System.out::println);
/ / 3,7,5
Copy the code

3.10 A small summary instance

      List<String> strings = Arrays.asList(""."lucas"."Hello"."HelloWorld"."Go Wuhan!");
        Stream<Integer> distinct = strings.stream()
                .filter(string -> string.length() <= 6) // filter out "HelloWorld"
                .map(String::length)  // Change Stream from String to int
                .sorted() //int from small to large
                .limit(2) // Select the first two
                .distinct(); / / to heavy
       distinct.forEach( System.out::println);
Copy the code

Running results:

0
4
Copy the code

Four Stream final operation

Terminating operations generate results from the pipeline of a stream. The result can be any value that is not a stream, such as List, Integer, and so on. The intermediate Stream operation returns a Stream. How do we convert a Stream to the desired type? And that requires terminal operation

The final operation consumes the flow, producing an end result. That is, the flow cannot be used again after the final operation, nor can any intermediate operation be used, or an exception will be thrown:

java.lang.IllegalStateException: stream has already been operated upon or closed
Copy the code

Common final operations are as follows:

methods describe
allMatch(Predicate p) Check that all elements match
anyMatch(Predicate p) Check that at least one element matches
noneMatch(Predicate p) Check that all elements are not matched
findFirst() Returns the first element
findAny() Returns any element in the current stream
count() Returns the total number of elements in the stream
max(Comparator c) Returns the maximum value in the stream
min(Comparator c) Returns the minimum value in the stream
forEach(Consumer c) Internal iteration (Using the Collection interface requires the user to do iteration, called external iteration. Instead, the Stream API uses internal iteration — it does the iteration for you.)

4.1 the forEach method

Stream provides the method ‘forEach’ to iterate over each data in the Stream. Note that the argument to the forEach method is Cousumer.

The following code snippet prints 10 random numbers using forEach:

Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
Copy the code

4.2 the count method

Count is used to count the number of elements in the stream.

    List<String> strings = Arrays.asList(""."lucas"."Hello"."HelloWorld"."Go Wuhan!");
 
    long count = strings.stream()
            .filter(string -> string.length() <= 6)
            .map(String::length)
            .sorted()
            .limit(2)
            .distinct()
            .count();
    System.out.println(count);
    / / 2
Copy the code

4.3 collect method

There are many static methods in collect, and flexible use of them will play a great role.

Collect is a reduction operation that accepts various practices as arguments to accumulate elements in a stream into a summary result:

    List<String> strings = Arrays.asList(""."lucas"."Hello"."HelloWorld"."Go Wuhan!");
 
    List<Integer> collect = strings
            .stream()
            .filter(string -> string.length() <= 6)
            .map(String::length)
            .sorted()
            .limit(2)
            .distinct()
            .collect(Collectors.toList());
    collect.forEach(System.out::println);

// Run the result
0
4
Copy the code

As above, you end up with a List array, which is where the stream ends up.

The preceding example is only a simple List generation example. Collectors provides more convenient operations for developers to use. You can use various static methods provided by the Collectors class to realize diversified requirements.

Some common static methods provided by the Collectors class:

methods The return type role The instance
toList List Collect elements from the stream into the List List emps= list.stream().collect(Collectors.toList());
toSet Set Collect elements from the stream into a Set Set emps= list.stream().collect(Collectors.toSet());
toCollection Collection Collect elements from the stream into the created collection Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long Count the number of elements in the stream long count = list.stream().collect(Collectors.counting());
summingInt Integer Sum of integer attributes of elements in the flow inttotal=list.stream().collect(Collectors.summingInt(Employee::getSalary));
joining String Concatenate each string in the stream String str= list.stream().map(Employee::getName).collect(Collectors.joining());
groupingBy Map<K, List> According to a certain attribute value, the convection is grouped, the genus is K, and the result is V Map<Emp.Status, List> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean, List> Partition according to true or false Map<Boolean,List>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));
reducing The type of reduction produced Starting with an initial value that acts as an accumulator, the BinaryOperator is combined with elements in the stream one by one to reduce to a single value inttotal=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
averagingInt Double Calculates the average value of the Integer attribute of the element in the stream doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
maxBy Optional Select the maximum value based on the comparator Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional Select the minimum value based on the comparator Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
summarizingInt IntSummaryStatistics Collect statistics for the Integer attribute in the stream. For example, average value IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
collectingAndThen Converts the type returned by the function Wrap another collector, and execute a conversion function on it inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

Here’s an example of grouping and multilevel grouping:

    List<Person> person_list = Arrays.asList(
            new Person("lucas".26),
            new Person("lucas".25),
            new Person("lucas".60),
            new Person( "chris".27),
            new Person( "chris".20),
            new Person("theo".52)); System.out.println("-------- Primary grouping --------- using the Collectors static method");
    Map<String, List<Person>> group = person_list.stream()
            .collect(Collectors.groupingBy(Person::getName));
    System.out.println(group);
 
    System.out.println("-------- Secondary grouping, using a static method, ---------");
    Map<String, Map<String, List<Person>>> group2 = person_list.stream()
            .collect(Collectors.groupingBy(Person::getName, Collectors.groupingBy((people) -> {
                if (people.getAge() < 30) return "Youth";
                else if (people.getAge() < 50) return "Middle-aged";
                else return "Old age";
            })));
    System.out.println(group);
Copy the code

Running results:

-------- Use the static method to group Collectors --------- {Chris =[cn.lucas.Person@52cc8049, cn.lucas.Person@5b6f7412], lucas=[cn.lucas.Person@27973e9b, cn.lucas.Person@312b1dae, cn.lucas.Person@7530d0a], Theo =[cn.lucas.Person@27bc2616]} -------- Use the static method to group Collectors: --------- {Chris ={young =[cn.lucas.Person@52cc8049, cn.lucas.Person@5b6f7412]}, Lucas ={youth =[cn.lucas.Person@27973e9b, cn.lucas.Person@312b1dae], Old =[cn.lucas.Person@7530d0a]}, theo={old =[cn.lucas.Person@27bc2616]}}Copy the code

It can be seen that the method in Collect can be skillfully used to realize how convenient and elegant some “dirty work, tired work and work without much value” (the above logic can be directly used in the List data processing process of a pile of data secondary List display). We need to focus, and the bulk of the code is on the business logic of the code.

One more Collectors. PartitioningBy segmentation examples of data blocks, they will make you feel simple and elegant:

public static void main(String[] args) {
 
    Map<Boolean, List<Integer>> collectParti = Stream.of(1.2.3.4)
            .collect(Collectors.partitioningBy(it -> it % 2= =0));
    System.out.println("Split data block:" + collectParti);
 
}
// Split data block: {false=[1, 3], true=[2, 4]}
Copy the code

4.4 findFirst/anyMatch/Max/min, and other methods

        List<Person> person_list = Arrays.asList(
                new Person("lucas".26),
                new Person("maggie".25),
                new Person("queen".23),
                new Person( "chris".27),
                new Person( "max".29),
                new Person("theo".30)); System.out.println("-------- allMatch() checks if all elements match ---------");
        boolean allMatch = person_list.stream()
                .allMatch((person -> person.getName().equals("lucas")));
        System.out.println(allMatch);
 
        System.out.println("-------- anyMatch() checks if at least one element is matched ---------");
        boolean anyMatch = person_list.stream()
                .anyMatch(person -> person.getName().equals("lucas"));
        System.out.println(anyMatch);
 
        System.out.println("-------- noneMatch checks if all elements are not matched ---------");
        boolean noneMatch = person_list.stream()
                .noneMatch(person -> person.getName().equals("lucas"));
        System.out.println(noneMatch);
 
        System.out.println("-------- findFirst() returns the first element ---------");
        Optional<String> first = person_list.stream()
                .map(Person::getName)
                .findFirst(); // Get the first element
        System.out.println(first.get());
 
        System.out.println("-------- findAny() returns any element in the current stream ---------");
        boolean anyMatch1 = person_list.stream()
                .anyMatch(person -> person.getName().equals("lucas"));
        System.out.println(anyMatch1);
 
        System.out.println("-------- Max () returns the maximum value in the stream ---------");
        Optional<Integer> intOptional = person_list.stream()
                .map(Person::getAge)
                .max(Integer::compare); / / Max
        System.out.println(intOptional.get());
Copy the code

Running results:

-- -- -- -- -- -- -- -- allMatch () to check whether all matched elements -- -- -- -- -- -- -- -- -- false -- -- -- -- -- -- -- -- anyMatch () check whether the matching at least one element -- -- -- -- -- -- -- -- - true -- -- -- -- -- -- -- -- noneMatch Check whether there is no matching all elements -- -- -- -- -- -- -- -- -- false -- -- -- -- -- -- -- -- findFirst () returns the first element -- -- -- -- -- -- -- -- -- Lucas -- -- -- -- -- -- -- -- findAny () returns the current flow of any element -- -- -- -- -- -- -- -- - true -------- Max () returns the maximum value in the stream --------- 30Copy the code

reference

Juejin. Im/post / 5 df4a9…

www.jianshu.com/p/2b40fd076…

Blog.csdn.net/liudongdong…

www.jianshu.com/p/e6cdc48bb…

Docs.oracle.com/javase/8/do…

www.jianshu.com/p/71bee9a62…

Blog.csdn.net/u012706811/…

www.fulinlin.com/2019/07/25/…