Introduction to the

Stream is a new way for Java 8 to work with arrays, collections, and so on. A Stream is like a factory pipeline on which developers can add processing operations to elements.

The resources

  1. Understand the implementation of Stream in Java8
  2. JAVA Stream Stream stateful and stateless operations

concept

Lambda

Lambda is a new feature in Java 8 that allows developers to use Lambda for functional programming by writing functional interfaces. A large number of methods in Stream use functional interfaces, so understanding Lambda is fundamental to understanding subsequent Stream code.

Functional interface

/** * a single unimplemented method interface (functional interface) *@FunctionalInterfaceAnnotations constrain an interface to have only one unimplemented method */
@FunctionalInterface 	// Identifies a functional interface
interface TestFunctionalInterface{
    int add(int b);
}
Copy the code

Lambda

// b is the parameter of the interface add method. The number of parameters is the same as that of the interface method
// -> point to the implementation method
// {} interface implementation method
TestFunctionalInterface testFunc = (b)->{
    return b+1;
};
Copy the code

The use of Lambda

package com.studyjava.stream;

public class Java8Lambda {
    /** * there is only one method in the interface that needs to be implemented, using@FunctionInterfaceAnnotated, this is the functional interface */
    @FunctionalInterface
    interface TestFunctionalInterface{
        int add(int b);
    }


    public static void main(String[] args){
        // Declare variables separately
        System.out.println("Use separate declaration variables");
        TestFunctionalInterface testFunc = (b)->{   / / using the Lambda
            return b+1;
        };
        int result = testFunc.add(10);
        System.out.println("result=>" + result + "\n");

        // used in method arguments
        System.out.println("Used in method parameters");
        result = addOne((b)->{ return b+1; });/ / using the Lambda
        System.out.println("result=>" + result + "\n");

        // Declare variables to be passed to method arguments
        System.out.println("Declare variables passed to method parameters using");
        TestFunctionalInterface testFunc2 = (b)->{ / / using the Lambda
            return b+1;
        };
        result = addOne(testFunc2);
        System.out.println("result=>" + result + "\n");

    }
    public static int addOne(TestFunctionalInterface testFunc){
        return testFunc.add(10) +1; }}Copy the code

Stream

This forms a Stream, as shown in the figure above. We can see the components of Stream:

  1. Data source – List
  2. Flowing elements – Elements in the List
  3. Intermediate operation – (Map, filter)
  4. End Operation – (collect)

Sample code corresponding to the image above:

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Java8Stream {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");

        System.out.println("before list ===>" + list);
        List<Integer> results = list.stream()
                .map(item -> Integer.parseInt(item)) // Convert String to Integer
                .filter(item -> item < 4)			 // Filter the list of items less than 4
                .collect(Collectors.toList());       // Collect results
        System.out.println("after list ===>"+ results); }}// The result of the program running
/*
before list ===>[1, 2, 3, 4, 5]
after list ===>[1, 2, 3]
*/
Copy the code

PS: map().filter(); Instead, the element loops through the map method, returns the result, and then loops through the filter. The two methods share a loop.

Equivalent to the following code:

List<Integer> result = new ArrayList<>();        // Collect results
for(String item: list){
	Integer int = Integer.parseInt(item);    // Data conversion
	if( item > 4) {// Data filteringresult.add(item); }}Copy the code

Just like the pipeline above, the elements in the array are processed first by the Map method and then by the Filter method.

The data source

The data source is the source of the element to be processed in the Stream. Data sources can be collections or arrays.

        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("3");
        list.add("2");
        list.add("5");
        list.add("4");
        Stream<String> listStream = list.stream(); //list as the data source

	String[] strArr = new String[10];
	strArr[0] ="1";
	strArr[1] ="2";
	strArr[2] ="3";
	strArr[3] ="4";
	strArr[4] ="5";
	Stream<String> arrStream = Arrays.stream(strArr); // Array as data source
Copy the code

In the middle of operation

Intermediate operation: In Stream, the operation to process elements. For example: transform (map), filter (filter), sort (sorted)

Features:

  1. After the intermediate operation is set, a Stream object is returned. The developer can add the intermediate action again or end the action.
  2. When an intermediate action is set, no intermediate action is performed.

Intermediate operations are classified as stateful and stateless

Stateful operations are operations that record their state internally while they are being performed.

For example, sorted, which collects all the elements in a list and then sorts them. When the sorting is complete, iterate again for output to the next intermediate operation or end operation

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(3);
        list.add(2);
        list.add(5);
        list.add(4);

        System.out.println("before list ===>" + list);
        List<Integer> results = list.stream()
                .sorted()			                 // Collect first, sort later
                .collect(Collectors.toList());       // Collect results
        System.out.println("after list ===>" + results);

Copy the code

Stateless operations are the processing of operations that focus only on the current element itself.

For example, map and filter

End of operation

Closing operation: The collection operation of the last element. For example, collect, reduce, and forEach

Features:

  1. The operation ends without returning a Stream object
  2. The intermediate action is performed only when the result action is executed

End operations include short-circuit operations and non-short-circuit operations

  1. Short-circuit operation means that when traversing an element, the system returns the element that meets the condition and does not traverse any more
    1. Examples: allMatch(), noneMatch(), findFirst(), findAny()
  2. Non-short-circuiting operations, as opposed to short-circuiting operations, complete the data source traversal.
    1. For example, forEach(), forEachOrdered(), toArray(), reduce(), collect(), Max (), min(), count()

Parallel computing

Stream is serial by default, and parallel computing is enabled as follows:

        List<Integer> results = list.stream()
                .parallel()							 // Enable parallelism
                .map(item -> Integer.parseInt(item)) 
                .filter(item -> item < 4)			 
                .collect(Collectors.toList());    
Copy the code

Simply call the Parallel method before finishing the operation to start parallelism.

Parallel computing relies on the ForkJoin framework provided by Java7 to split a task into several smaller tasks and then merge the results of the smaller tasks.

Note when using parallelism:

  1. Avoid using external state in intermediate operations
    1. Reason: The fundamental problem with multithreaded security is state shared with other threads.
    2. Parallel execution may result in inconsistent observed state from thread to thread
  2. Handle primitive type data, using a wrapped Stream. Such as: LongStream, IntStream
  3. Simple data processing uses serial rather than parallel processing
    1. ForkJoin takes time to split tasks.
    2. Multithreaded context switching takes time

Test code:

package com.studyjava.stream;

import com.sun.deploy.util.StringUtils;
import org.junit.Test;

import java.util.*;
import java.util.stream.BaseStream;
import java.util.stream.LongStream;

public class Java8StreamParallel {
    /** * In longStream, parallelism is faster than serial */
    @Test
    public void longStreamParallel(a) {
        // The boxing class is computed in parallel
        // Parallel computation
        long time = System.currentTimeMillis();
        long sum1 = LongStream.rangeClosed(1.100000000000l).parallel().map(log -> log * 10).sum();
        System.out.println(System.currentTimeMillis() - time);
        // serial computation
        time = System.currentTimeMillis();
        long sum2 = LongStream.rangeClosed(1.100000000000l).map(log -> log * 10).sum();
        System.out.println(System.currentTimeMillis() - time);

        System.out.println("sum1 = " + sum1 + " sum2 = " + sum2);
    }

    /** * Simple data processing in a List uses parallel processing slower than serial processing */
    @Test
    public void singleParallel(a) {
        //ArrayList parallel computation
        List<Long> arrayList = new LinkedList<>();
        for (long i = 0; i < 1000; i++) {
            arrayList.add(i);
        }
        long time = System.currentTimeMillis();
        arrayList.parallelStream()
                .map(log ->
                        log * 10
                ).reduce(0L, Long::sum);
        long end = System.currentTimeMillis() - time;
        System.out.println("Parallel end time--->" + end);
        time = System.currentTimeMillis();
        arrayList.stream()
                .map(log ->
                        log * 10
                ).reduce(0L, Long::sum);
        end = System.currentTimeMillis() - time;
        System.out.println("Sequential end time--->" + end);
    }

    /** * Complex data processing in a List uses parallel processing faster than serial processing */
    @Test
    public void arrayParallel(a) {
        //ArrayList parallel computation
        List<Long> arrayList = new LinkedList<>();
        for (long i = 0; i < 1000; i++) {
            arrayList.add(i);
        }
        long time = System.currentTimeMillis();
        arrayList.parallelStream().map(log -> {
            try {
                // It takes 1ms to simulate complex data processing
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return log * 10;
        }).reduce(0L, (i, a) -> i + a);
        long end = System.currentTimeMillis() - time;
        System.out.println("Parallel end time--->" + end);
        time = System.currentTimeMillis();
        arrayList.stream().map(log -> {
            try {
                // It takes 1ms to simulate complex data processing
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return log * 10;
        }).reduce(0L, (i, a) -> i + a);
        end = System.currentTimeMillis() - time;
        System.out.println("Sequential end time--->"+ end); }}Copy the code

The commonly used

The data source

  1. The base type contains Stream
    1. IntStream
    2. LongStream
    3. DoubleStream
  2. Collection classes
    1. List
    2. Set
    3. Map
  3. Array (conversion required)
    1. Stream.of()
      1. Internal call to arrays.stream ()
    2. Arrays.stream()

In the middle of operation

  1. map
    1. mapToInt
    2. mapToLong
    3. mapToDouble
  2. filter
  3. sorted

Results the operation

  1. Collect – Collect results
    1. Collect (Collectors. ToList) – The result changes to a List
  2. The forEach – traversal
  3. The count – count
  4. Sum-sum (can only be used if converted to LongStream)

The sample

package com.studyjava.stream;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;

public class Java8Stream {
    private static long getCurrentTime(a) {
        return System.currentTimeMillis();
    }
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");

        / / collect - collection
        System.out.println("Collect");
        System.out.println("before list ===>" + list);
        List<Integer> intList = list.stream()
                .map(item -> Integer.parseInt(item))   // Convert the data type
                .filter(item -> item < 4)              // Filter data
                .collect(Collectors.toList());         // Collect results
        System.out.println("after list ===>" + intList + "\n");
        / * the execution result before the list = = = > [1, 2, 3, 4, 5] after the list = = = > [1, 2, 3] * /

        / / count - count
        System.out.println("Count - count");
        System.out.println("before list ===>" + list);
        long count = list.stream()
                .map(item -> Integer.parseInt(item))   // Convert the data type
                .filter(item -> item < 4)              // Filter data
                .count();                              / / count
        System.out.println("count ===>" + count + "\n");
        / * the execution result before the list = = = > [1, 2, 3, 4, 5] count = = = > 3 * /

        / / the sum - sum
        System.out.println("Sum - sum");
        System.out.println("before list ===>" + list);
        long sum = list.stream()
                .mapToLong(item -> Long.parseLong(item)) // Convert data type (mapToLong)
                .filter(item -> item < 4)              	 // Filter data
                .sum();                              	 / / count
        System.out.println("sum ===>" + sum + "\n");
        / * the execution result before the list = = = > [1, 2, 3, 4, 5] sum = = = > 6 * /

        / / forEach - traversal
        System.out.println("Foreach-traversal");
        list.stream()
                .mapToLong(item -> Integer.parseInt(item))   // Convert the data type
                .filter(item -> item < 4)                    // Filter data
                .forEach(item -> System.out.println(item));  / / traverse
	Before list ===>[1, 2, 3, 4, 5] 1 2 3 */}}Copy the code

conclusion

  1. Enhanced code logic
    1. Functional programming: Lambda and Stream are used to simplify code and highlight data processing logic
    2. Chain call: data processing logic is joined to one another, linear logic is consistent with the way of thinking
    3. Deferred execution: The Stream is executed only when the call terminates the operation. This means that different intermediate operations can be concatenated according to the business logic before the call terminates the operation
  2. Parallel computing is simple to implement
    1. Stream blocks the traversal step of the data Stream, allowing the developer to focus on the processing of elements. The realization of parallel computing is entrusted to the underlying code, and the developer only needs to call the parallel computing method provided by the underlying code to enable parallel computing.