[Stream] summary of new features of java8

1, What is stream

  • A new abstract interface Stream API is added in java8. Using Stream operation set is similar to using SQL statement to find data in database, providing intuitive methods for operation.

  • Stream regards the set of elements to be processed as a stream, which is transmitted in the pipeline and can be processed by convection in the process of pipeline transmission, such as filtering, sorting, aggregation and so on

  • A Java8 stream is composed of three parts. Data source, zero or one or more intermediate operations, one or zero termination operations.

  • The intermediate operation is the processing of data. Note that the intermediate operation is a lazy operation and will not be started immediately. It will not be executed until the operation is terminated. The termination operation is the start operation of the stream. Only when the termination operation is added can the stream really start to execute.

2, Stream operation classification

Stream operation is divided into intermediate operation and terminal operation. The information is as follows:

Stream operation classification
Intermediate operationStatelessunordered(),filter(),map(),mapToInt(),mapToLong() mapToDouble(),flatMap(),flatMapToInt(),flatMapToLong() flatMapToDouble(),peek()
Statefuldistinct(),sorted(),limit(),skip()
Terminal operationNon short circuit operationforEach(),forEachOrdered(),toArray(),reduce(),collect,max(),min(),count()
Short circuit operationanyMatch(),allMatch(),noneMatch(),findFirst(),findAny()
  • Intermediate operation: intermediate operation is actually logical processing. This operation can have one or more continuous operations to convert one stream into another. These operations will not consume the stream. Its purpose is to establish a pipeline and do the final execution of data until the terminal operation occurs.
    • Stateless: data processing is not affected by previous "intermediate operations"

    • Stateful: it means that stateful methods often require greater performance overhead due to the influence of previous "intermediate operations" during data processing

  • Terminal operation: a Stream object can only have one terminal operation. Once this operation occurs, it will actually process the data and generate the corresponding processing results
    • Non short circuit operation: it means that all elements must be processed to get the final result

    • Short circuit operation: it means that the final result can be obtained when some qualified elements are encountered

3, Stream properties

  • Do not store data: Stream does not store data, but processes data according to specific rules

  • Do not change the data source: Stream usually does not change the original data source. During operation, it usually creates a copy of the original data source, and then processes the copy to generate a new Stream

  • Laziness: many Stream operations have backward delays. It needs to wait until it knows how much data it needs in the end. The intermediate operation is always inert

  • It can be infinite: a collection has a fixed size, while a Stream does not. Short circuit operations such as limit (n) and findFirst () can quickly complete the operation and processing of infinite streams.

  • Support parallel capability: Stream supports parallel capability and can easily perform parallelization and process data

  • Consumable: in the life cycle of a Stream, the elements of the Stream are accessed only once. Like iterators, you must generate a new Stream before you can revisit the same elements as in the start source.

4, Convert data to Stream

Generally, we need to obtain the Stream object before we can operate on it. Here are some common methods for converting data into Stream:

    /** Convert data to Stream */
    @Test
    public void a1() {
        // Multiple data are directly converted to Stream
        Stream<String> stream1 = Stream.of("a", "b", "c");
        System.out.println(stream1);  // java.util.stream.ReferencePipeline$Head@33d53216
        // Convert array to Stream
        String[] strArrays = {"a", "b"};
        Stream<String> stream2 = Arrays.stream(strArrays);
        System.out.println(stream2);  // java.util.stream.ReferencePipeline$Head@69a2b3b6
        // Convert list to Stream
        ArrayList<String> strList = new ArrayList<>();
        strList.add("a");
        strList.add("b");
        Stream<String> stream3 = strList.stream();
        System.out.println(stream3);  // java.util.stream.ReferencePipeline$Head@4f3e7344
        // Convert set to Stream
        Set<String> strSet = new HashSet<>();
        strSet.add("a");
        strSet.add("b");
        Stream<String> stream4 = strSet.stream();
        System.out.println(stream4);   // java.util.stream.ReferencePipeline$Head@7808f638
        // Convert map set to Stream
        HashMap<String, Integer> map = new HashMap<>();
        map.put("a", 100);
        map.put("b", 200);
        Stream<Map.Entry<String, Integer>> stream5 = map.entrySet().stream();
        System.out.println(stream5);  // java.util.stream.ReferencePipeline$Head@62d73ead
    }

5, Stream conversion to get data of the specified type

Since you can convert a collection or array into a stream, you can also convert the stream back, which can be realized by using the collect () method. However, it generally needs to be used together with the Collectors tool class. A series of collector implementations are built in the Collectors class, as follows:

  • toList (): collect elements into a new List collection

  • toSet(): add elements to a new Set set

  • toCollection (): collect elements into a new ArrayList collection

  • joining (): collects elements into a string that can be specified with a delimiter

/** Stream Convert to the specified type of data */
    @Test
    public void a2() {
        // Stream conversion bit string
        Stream<String> stream2 = Stream.of("a", "b", "c", "a");
        String str = stream2.collect(Collectors.joining()).toString();
        System.out.println(str);   // abca
        // Convert Stream to List
        List<String> strList = stream2.collect(Collectors.toList());
        System.out.println(strList);   // [a, b, c, a]
        // Convert Stream to Set collection
        Set<String> collect1 = stream2.collect(Collectors.toSet());
        System.out.println(collect1);  // [a, b, c]
    }

6, Stream encapsulation of basic types

Because Java generics do not support basic types, we cannot use types such as Stream < int >. If we write similar code, a compilation error will be prompted in the compiler. In order to store int, only Stream < integer > can be used, but this will cause frequent boxing and unpacking operations. In order to improve efficiency, the basic types int, long and double are encapsulated, which are IntStream, LongStream and DoubleStream respectively. The use method is similar to that of Stream. The example code is as follows:

/** Stream For basic type encapsulation */
    @Test
    public void a4() {
        // IntStream
        IntStream.of(new int[]{10,20,30}).forEach(System.out::println); // 10 20 30
        IntStream.range(1,5).forEach(System.out::println);          // 1 2 3 4
        IntStream.rangeClosed(1,5).forEach(System.out::println);    // 1 2 3 4 5
        // LongStream
        LongStream.of(new long[]{1L, 2L, 3L}).forEach(System.out::println);  // 1 2 3
        LongStream.range(1,5).forEach(System.out::println);          // 1 2 3 4
        LongStream.rangeClosed(1,5).forEach(System.out::println);    // 1 2 3 4 5
        // DoubleStream
        DoubleStream.of(1.11, 2.23, 3.14).forEach(System.out::println);  // 1.11  2.23  3.14
    }

7, Serial and parallel of Stream

  1. Introduction to Stream parallelism

In stream, the most obvious feature is the existence of parallel operations. However, if the default mode is used to perform intermediate and terminal operations, the whole execution process is actually a serial operation. If you want Stream to process data in parallel, you need to invoke parallel () in Stream or invoke parallelStream () method in the collection to open parallel execution.

@Test
public void a5() {
    List<String> list = Arrays.asList("One", "Two", "three", "Four", "Five");
    Stream<String> stream = list.stream().parallel();
    System.out.println(stream);
	// The second way to write
    Stream<String> stringStream = list.parallelStream();
    System.out.println(stringStream);
}

Among them, the bottom layer of Stream uses ForkJoinTask to realize the parallel processing of Stream, making full use of the multi-core ability of CPU. The API of Stream completely shields the complex implementation of the bottom layer. Developers only need to call one method to realize parallel computing.

      2. Serial and parallel run time of stream

@Test
    public void a5() {
        //Parallel computing
        long startTime = System.currentTimeMillis();
        System.out.println(startTime);
        long sumResult1 = LongStream.rangeClosed(1, 10000000000L).parallel().sum();
        System.out.println(System.currentTimeMillis() - startTime);  // 1384
        // Serial computing
        startTime = System.currentTimeMillis();
        long sumResult2 = LongStream.rangeClosed(1, 10000000000L).sum();
        System.out.println(System.currentTimeMillis() - startTime);  // 4134
    }

It can be seen that the time spent using parallel execution is much lower than that spent in serial execution. However, when using parallel execution, we must first consider the use situation, whether the execution data needs to be executed in sequence, whether thread safety is involved, whether the use of network is involved, etc.

8, Stream intermediate operation (stateful) common API

1. distinct weight removal

  • Ensure that the output stream contains unique elements, which is usually used for data De duplication.
  • Interface definition: stream < T > distinct();
  • Method description: no parameters are received in the distinct interface definition. The function of this method is to obtain different elements according to hashCode() and equals() methods. Therefore, our elements must implement these two methods. If distinct() is processing an ordered stream, the order in which the elements were created is preserved for repeating elements. In the case of disordered flow, the order of elements is not necessarily guaranteed. In the case of parallel execution of ordered streams, maintaining the order of distinct() requires high buffer overhead. If we don't need to guarantee the order of elements when dealing with elements, we can use unordered() method to realize unordered flow.
/** distinct() method */
    @Test
    public void a6() {
        List<Integer> strList = new ArrayList<>();
        strList.add(4);strList.add(2);strList.add(2);strList.add(1);strList.add(3);
        strList.stream().distinct().unordered().forEach(System.out::println);  // 4 2 1 3
    }
// The case advantage in the project is that there is no need to write duplicate sql statements
// Stateful: it means that stateful methods often require greater performance overhead due to the influence of previous "intermediate operations" during data processing
List<String> orgIdList = programDimensionList.stream().map(BackcalProgramDimension::getOrganizationId).distinct().collect(Collectors.toList());

2. sorted

Sort the data, but for ordered flow, the sorting is stable, while for non ordered flow, the sorting is not guaranteed to be stable.

  • Interface definition: stream < T > sorted();    Stream<T> sorted(Comparator<? super T> comparator);
  • Method description:
    • Use Stream < T > sorted(); Method, it sorts the elements in the Stream in a natural way. After all the elements are processed, the elements are formed into a new Stream and returned.

    • Use Stream < T > sorted (Comparator <? Super T > Comparator); Method, it receives a Comparator type parameter. In Lambda expression, Comparator < T > is generally used to compare two parameters, design an algorithm logic, and return an integer after execution, which can be negative, zero or positive integer. Its different values represent the difference between the two values. Generally, the sorting will be sorted according to the comparison result. After all the elements are processed, the elements are formed into a new Stream and returned.

/** sorted method */
    @Test
    public void a7() {
        List<Integer> strList = new ArrayList<>();
        strList.add(4);strList.add(2);strList.add(1);
        strList.stream().sorted((x, y) -> x-y).forEach(System.out::println); // 1 2 4 comparator is used for comparison from small to large
    }
// The sorting used in the project is not natural sorting. It is compared through the comparator. If it is from high to low, reverse () is added to the back of the comparator, and the value of getVariableCode is obtained at last
List<String> variableCodeList = variableList.stream()
    .sorted(Comparator.comparing(CalculationVariable::getVariableCode).reversed())
    .map(f -> f.getVariableCode())
    .distinct()
    .collect(Collectors.toList());

3. skip

Skips some elements in the stream from the specified position according to the specified value

  • Interface definition: sream < T > skip (long n);
  • Method description: in the skip interface definition, the long type parameter is received. It specifies to skip the first n elements and return the n-th element into a new stream
/** skip method */
    @Test
    public void a8() {
        List<Integer> strList = new ArrayList<>();
        strList.add(1);strList.add(2);strList.add(3);
        strList.stream().skip(2).forEach(System.out::println);  // 3
    }

4. Limit limit display

According to the specified value, limit the maximum number of accesses in the access flow. This is a stateful, short-circuit method.

  • Interface definition: stream < T > limit (long maxsize);
  • Method description: in the definition of the limit interface, it is to receive the long type parameter and specify the limit of maxSize elements, that is, maxSize and its previous elements form a new stream return
/** limit method */
    @Test
    public void a9() {
        List<Integer> strList = new ArrayList<>();
        strList.add(1);strList.add(2);strList.add(3);strList.add(4);strList.add(5);
        strList.stream().limit(2).forEach(System.out::println);  //  1  2
    }

9, Stream intermediate operation (stateless) common API

        1,map

The elements in the stream are processed and then returned. The type of the returned value can be different from that of the original element

  • Interface definition: stream < R > map (function <? Super T,? Extends r > mapper);
  • Method description: in the map interface definition, it is to receive Function type parameters. By understanding Lambda expression, you can know that Function < T, R > is to receive a T and return the processed value R. Therefore, the map method here is to process the elements in the stream and then return a new element. When all the elements are processed, the elements form a new stream and return.
/** map */
    @Test
    public void a10() {
        List<String> strList = new ArrayList<>();
  strList.add("zhangsan");strList.add("lisi");strList.add("wangwu");strList.add("zhaoliu");
        strList.add("sunqi");
        strList.stream().map(x -> "Test:" + x).collect(Collectors.toList()).forEach(System.out::println);
    }
Test: zhangsan
 Test: lisi
 Test: wangwu
 Test: zhaoliu
 Test: sunqi
// Cases in the project
// Judge whether the appraisal period exists in the appraisal period table
boolean isExitPeriod = periodList.stream().map(Period::getPeriod).collect(Collectors.toList()).contains(period);

        2,peek

Each element in the stream is manipulated, and the returned stream holds the same elements as the original stream

  • Interface definition: stream < T > Peek (consumer <? Super T > action);
  • Method description: in the peek interface definition, it is to receive the Consumer type parameter. By understanding the Lambda expression, you can know that Consumer < T > receives a t return and does not return a value after processing. Therefore, the peek method here is to process the elements in the stream without returning a value. When all the elements are processed, the elements form a new stream and return.
/** peek */
    @Test
    public void a11() {
        List<String> strList = new ArrayList<>();
        strList.add("zhangsan");
        strList.add("lisi");
        strList.add("wangwu");
        strList.stream().peek(x -> System.out.println("forEach 1:" + x))
                .peek(x -> System.out.println("forEach 2:" + x))
                .forEach(System.out::println);
    }

forEach 1:zhangsan
forEach 2:zhangsan
zhangsan
forEach 1:lisi
forEach 2:lisi
lisi
forEach 1:wangwu
forEach 2:wangwu
wangwu

My understanding of peek is to output a single value to the elements in the stream stream, which is convenient for modulation and viewing the value of each variable.

        3,filter

It is mainly used for data filtering, filtering elements that do not meet the predicate conditions, and retaining the filtered elements

  • Interface definition: stream < T > filter (predict <? Super T > predict);
  • Method description: in the filter interface definition, it is to receive the Predicate type parameter. By understanding the Lambda expression, you can know that Predicate < T > receives a t for verification and returns a Boolean value. Therefore, the filter method here is to set the verification conditions, verify the elements in the stream, and return all qualified elements to form a new stream.
/** filter */
    @Test
    public void a12() {
        List<String> strList = new ArrayList<>();
        strList.add("zhangsan");strList.add("lisi");strList.add("wangwu");strList.add("zhaoliu");strList.add("sunqi");
        strList.stream().filter(x -> x.length()>5).collect(Collectors.toList()).forEach(System.out::println);
    }
zhangsan
wangwu
zhaoliu

        4,mapToInt

It is mainly used for one-to-one conversion of elements in convection to int integers, and then some summation, average value, maximum and minimum value and other processing can be carried out.

  • Interface definition: intstream maptoint (tointfunction <? Super T > mapper);
  • Method description: it can be seen from the mapToInt interface definition that it receives ToIntFunction type parameters. In Lambda, the function of ToIntFunction < T > is to receive an input parameter and return an int type result. According to this, it is easy to understand that the mapToInt method is to convert the original element type of Stream into int type into a new Stream.
/** mapToInt */
    @Test
    public void a13() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        int max = integerStream.mapToInt(x -> x).summaryStatistics().getMax();
        System.out.println(max);  // 10
        IntSummaryStatistics intSummaryStatistics = integerStream.mapToInt(x -> x).summaryStatistics();
        System.out.println(intSummaryStatistics);  // IntSummaryStatistics{count=10, sum=55, min=1, average=5.500000, max=10}
    }
// Examples used in the project:
workDay = BigDecimal.valueOf(workDayCountListGroupById.get(employeeId).stream().mapToInt(PorationModel::getCount).sum());

         5,mapToDouble

It is mainly used for one-to-one conversion of elements in convection to double double precision floating-point numbers, and then some summation, average value, maximum and minimum value and other processing can be carried out.

  • Interface definition: doublestream maptodouble (tointfunction <? Super T > mapper);
  • Method description: it can be seen from the mapToDouble interface definition that it receives the type parameter of ToDoubleFunction. In Lambda, the function of ToDoubleFunction < T > is to receive an input parameter and return a result of Double type. According to this, it is easy to understand that the mapToDouble method is to convert the original element type in the Stream into Double type into a new Stream.
/** mapToDouble */
    @Test
    public void a14() {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        double max = integerStream.mapToDouble(x -> x).summaryStatistics().getMax();
        System.out.println(max);  // 10.0
        DoubleSummaryStatistics doubleSummaryStatistics = integerStream.mapToDouble(x -> x).summaryStatistics();
        System.out.println(doubleSummaryStatistics);  // DoubleSummaryStatistics{count=10, sum=55.000000, min=1.000000, average=5.500000, max=10.000000}
    }

10, Common API for Stream terminal operation (short circuit operation)

        1,anyMatch      Detailed explanation of the differences between anyMatch, allMatch and noneMatch

        2,allMatch        Detailed explanation of the differences between anyMatch, allMatch and noneMatch

        3,noneMatch   Detailed explanation of the differences between anyMatch, allMatch and noneMatch

        4,findFirst

Returns the first element. If the stream is empty, an empty Optional object is returned.

  • Interface definition: optional < T > findfirst();
  • Method description: the function of findFirst method is to return the first object in the collection
/**
     * findFirst
     */
    @Test
    public void a21() {
        List<User> list = new ArrayList<>();
        list.add(new User("Zhang San", 12, "Nanjing"));
        list.add(new User("Li Si", 13, "Beijing"));
        list.add(new User("Wang Wu", 14, "Suzhou"));
        list.add(new User("Wang Wu", 17, "Suzhou"));
        Optional<User> user = list.stream().filter(a -> a.getName().equals("Wang Wu")).findFirst();
        if (user.isPresent()) {
            System.out.println(user.get().getAge());   // 14
        } else {
            System.out.println("No one was found");
        }
    }
// Examples used in the project
Optional<MailDataSourceRelation> relation = relationList.stream().filter(f -> f.getColumnKey().equals(item.getColumnKey())).findFirst();
if (relation.isPresent()) {
    item.setIsUsed("Y");
}

        5,findAny

Return any element. If the stream is empty, return an empty Optional object.

  • Interface definition: optional < T > findany();
  • Method description: the function of findAny method is to return any object in the collection.
/**
     * findAny
*/
@Test
public void a22() {
    List<User> list = new ArrayList<>();
    list.add(new User("Zhang San", 12, "Nanjing"));
    list.add(new User("Li Si", 13, "Beijing"));
    list.add(new User("Wang Wu", 14, "Suzhou"));
    list.add(new User("Wang Wu", 17, "Suzhou"));
    Optional<User> user = list.stream().filter(a -> a.getName().equals("Wang Wu")).findAny();
    if (user.isPresent()) {
        System.out.println(user.get().getAge());   // 14
    }
}
// Examples used in projects are often used in conjunction with isPresent
if(list.stream().filter(x -> !x.getId().equalsIgnoreCase(item.getId()) && x.getIndicatorCode().equalsIgnoreCase(item.getIndicatorCode())).findAny().isPresent()){
    throw ExpResult.fail("Weight item code already exists");
}

11, Common API for Stream terminal operation (non short circuit operation)

        1,max

Returns the maximum value of all elements in the stream

  • Interface definition: optional < T > max (comparator <? Super T > comparator);
  • Method description: receive Comparator type parameters in the max interface definition. The consumer < T > of Lambda common functions is generally used to compare two parameters, set an algorithm logic, and return an integer after execution, which can be negative, zero or positive integer. Sort and filter the maximum value according to the returned value results.
 /**
     * max use
*/
@Test
public void a24() {
    Stream<Integer> stream = Stream.of(1, 3, 5, 8);
    Optional<Integer> max = stream.max((x, y) -> x - y);
    if (max.isPresent()) {
        System.out.println(max.get());  // 8
    }
}

         2,min

Returns the minimum value of all elements in the stream

  • Interface definition: optional < T > min (comparator <? Super T > comparator);
  • Method description: receive Comparator type parameters in the min interface definition. The consumer < T > of Lambda common functions is generally used to compare two parameters, set an algorithm logic, and return an integer after execution, which can be negative, zero or positive integer. Sort and filter the minimum value according to the returned value results.
/**
 * min use
*/
@Test
public void a25() {
    Stream<Integer> stream = Stream.of(1, 3, 5, 8);
    Optional<Integer> min = stream.min(Integer::compareTo);
    if (min.isPresent()) {
        System.out.println(min.get());  // 1
    }
}
//Examples used in the project
Optional<MeasurementTierSetting> hopsOptional = hopsList.stream().min(Comparator.comparing(MeasurementTierSetting::getRowSeq));

         3,count

Returns the number of all elements in the stream

  • Interface definition: long count();
  • Method description: this method is used to count the number of elements in the stream and return long type results.
/**
     * count use
     */
@Test
public void a26() {
    Stream<Integer> stream = Stream.of(1, 3, 5, 8);
    long count = stream.count();
    System.out.println(count);  // 4
}
//Examples used in the project
long count = ruleAndCommissionItemRelationHistoryList.stream()
    .filter(e -> e.getCommissionItemId().equals(commissionItemId)).count();
if (count > 0) {
    commissionItemIds.add(commissionItemId);
}

         4,reduce

It is mainly used to merge data (add the values of fields or add the values of multiple fields)

  • Interface definition:

        5,forEach

It is mainly used to traverse the elements in the whole flow of data and execute the specified logic. The order is not guaranteed during concurrent execution.

  • Interface definition: void foreach (consumer <? Super T > action);
  • Method description: execute a piece of logic code for each element in the Stream. For example, print the value of the element in a loop. However, it should be noted that the execution order cannot be guaranteed during concurrent execution.
/**
     * forEach use
     */
@Test
public void a30() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    stream.forEach(System.out::println);
}

        6,forEachOrdered

It is mainly used to traverse the elements in the whole flow of data, execute the specified logic, and ensure the order during concurrent execution

  • Interface definition: void foreachordered (consumer <? Super T > action);
  • Method description: execute a piece of logic code for each element in the Stream. For example, print the value of the element in a loop. However, it should be noted that the execution order cannot be guaranteed during concurrent execution.
/**
     * forEachOrdered
     */
@Test
public void a31() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    stream.parallel().forEachOrdered(System.out::println);
}

        7,toArray

Store the data in the Stream into an array

  • Interface definition: Object[] toArray();     <A>A[] toArray(IntFunction<A[]> generator);
  • Method description: store the elements in the Stream into the Object array
/**
     * toArray
     */
@Test
public void a32() {
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Object[] objectArray = stream.toArray();
    Arrays.stream(objectArray).forEach(System.out::println);
}

        8,collect

Common aggregation methods can aggregate data. Not only that, it can also be used with the Controller to process the aggregated data.

Keywords: Java Spring Boot Back-end

Added by shlumph on Sat, 29 Jan 2022 11:15:12 +0200