2. Stream syntax of Java 8 feature detailed explanation

Preface

This article mainly introduces another feature of Java 8-Stream syntax.

1. What is Stream?

Official explanation

A sequence of elements supporting sequential and parallel aggregate operations.
//Stream是元素的集合,这点让Stream看起来用些类似Iterator;
//可以支持顺序和并行的对原Stream进行汇聚的操作;

In fact, a popular explanation: it is an executable block with input parameters, a function that may be called without being bound to an identifier.
You can think of Stream as an advanced version of Iterator. In the original version of Iterator, users can only traverse the elements one by one and perform certain operations on them; in the advanced version of Stream, users only need to specify what operations need to be performed on the contained elements, and how these operations are applied to each element , Just give it to Stream!

2. Stream syntax analysis

List<Integer> nums = Lists.newArrayList(1,null,3,4,null,6);
nums.stream().filter(num -> 
  num != null
).count();

Insert picture description here
The statement in the red box is the place where the life of a Stream begins, which is responsible for creating a Stream instance; the statement in the green box is the place where the soul of the Stream is given, and the statement in the red box is converted into another Stream. The statement in the red box generates a containing All streams of nums variables, after entering the filter method in the green box, regenerate a Stream after filtering out all nulls in the original nums list; the statement in the blue box is the place of aggregation, and the content contained in the stream is A kind of algorithm to converge into a value, the example is to get the number of elements contained in the Stream.

Here we summarize the basic steps of using Stream:

 1. 创建Stream;
 2. 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(**可以有多次转换**);
 3. 对Stream进行聚合(Reduce)操作,获取想要的结果;

2.1, create a Stream

There are two most common ways to create a Stream:

1. 通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
2. 通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法,后续文章会有介绍)–stream(),把一个Collection对象转换成Stream。

(1) Create Stream2.1 Use Stream static method to create Stream

  1. of method: There are two overload methods, one accepts variable length parameters, and one interface has a single value
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
  1. Generator method: Generate an infinite-length Stream, whose elements are generated through a given Supplier (this interface can be regarded as an object factory, and each call returns an object of a given type)
Stream.generate(new Supplier<Double>() {
    
    
   @Override
   public Double get() {
    
    
       return Math.random();
   }
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);

The functions of the three statements are the same, but the syntax of lambda expressions and method references is used to simplify the code. Each statement actually generates an infinite-length Stream, where the value is random. This infinite-length Stream is lazy loading. Generally, this infinite-length Stream will be used in conjunction with the limit() method of the Stream.
3. Iterate method: It also generates an infinite-length Stream. Unlike the generator, its element is generated by repeatedly calling a user-specified function for a given seed value (seed). The elements contained therein can be considered as: seed, f(seed), f(f(seed)) infinite loop

Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);

This code is to first get a Stream of an unlimited length set of positive integers, and then take the first 10 prints. Remember to use the limit method, otherwise it will print indefinitely.

(2) Obtaining Stream through Collection subclass.
In the first example of this article, it was shown to obtain the corresponding Stream object from the List object. If you check the Java doc, you can find that the Collection interface has a stream method, so all its subclasses Anyone can get the corresponding Stream object.

public interface Collection<E> extends Iterable<E> {
    
    
   //其他方法省略
   default Stream<E> stream() {
    
    
       return StreamSupport.stream(spliterator(), false);
   }
}

2.2, convert Stream

Converting a Stream is actually converting a Stream into a new Stream through certain behaviors. Several commonly used conversion methods are defined in the Stream interface. Below we select a few commonly used conversion methods to explain.

  1. Distinct: Deduplicate the elements contained in the Stream (the deduplication logic depends on the equals method of the elements), and there are no duplicate elements in the newly generated Stream;
    Insert picture description here
  2. filter: Use the given filter function to filter the elements contained in the Stream, and the newly generated Stream only contains elements that meet the conditions;
    schematic diagram of the filter method:
    Insert picture description here
  3. map: Use the given conversion function to convert the elements contained in the Stream, and the newly generated Stream only contains the elements generated by the conversion. This method has three variants for primitive types, namely: mapToInt, mapToLong and mapToDouble. These three methods are also relatively easy to understand. For example, mapToInt converts the original Stream into a new Stream. The elements in this newly generated Stream are all int types. The reason there are three variant methods can avoid the extra cost of automatic packing/unpacking;
    map method diagram:
    Insert picture description here
  4. flatMap: Similar to map, the difference is that each element is converted to a Stream object, which compresses the elements in the child Stream to the parent collection;
    schematic diagram of the flatMap method:
    Insert picture description here
  5. peek: Generate a new Stream containing all the elements of the original Stream, and provide a consumption function (Consumer instance). When each element of the new Stream is consumed, the given consumption function will be executed;
    schematic diagram of the peek method:
    Insert picture description here
  6. limit: Perform a truncation operation on a Stream to obtain the first N elements. If the number of elements contained in the original Stream is less than N, then obtain all of its elements;
    schematic diagram of the limit method:
    Insert picture description here
  7. skip: Return a new Stream composed of the remaining elements after discarding the first N elements of the original Stream. If the number of elements contained in the original Stream is less than N, then return an empty Stream;
    schematic diagram of skip method:
    Insert picture description here
  8. Together with!
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println(“sum is:+nums.stream().filter(num -> num != null).
           distinct().mapToInt(num -> num * 2).
           peek(System.out::println).skip(2).limit(4).sum());

This code demonstrates all the conversion methods introduced above (except flatMap), and briefly explains the meaning of this code: Given a List of Integer type, get its corresponding Stream object, then filter out nulls, and then remove duplicates. Then multiply each element by 2, and then print itself when each element is consumed, skip the first two elements, and finally go to the first four elements for addition and operation (a lot of explanation, very like nonsense, because it basically looks You know what to do after the method name. This is one of the great benefits of declarative programming!).
9. Performance issues
Some careful students may have this question: When performing multiple conversion operations on a Stream, each element of the Stream is converted each time, and it is executed multiple times, so the time complexity is a for N (the number of conversions) times that all operations are done in the loop. In fact, this is not the case. The conversion operations are all lazy, and multiple conversion operations will only be merged when converging operations (see the next section) and completed in one loop. We can simply understand that there is a collection of operation functions in Stream, and each conversion operation is to put the conversion function into this collection, and loop the corresponding collection of Stream during the aggregation operation, and then execute all functions for each element .

2.3. Reduce Stream

The aggregation operation (also called folding) accepts a sequence of elements as input, and repeatedly uses a certain merge operation to merge the elements in the sequence into a summary result. For example, find the sum or maximum value of a list of numbers, or accumulate these numbers into a List object. The Stream interface has some general aggregation operations, such as reduce() and collect(); there are also some specific-purpose aggregation operations, such as sum(), max(), and count(). Note: The sum method is not available for all Stream objects, only IntStream, LongStream and DoubleStream are instances.

The following will be divided into two parts to introduce the convergence operation:

1. 可变汇聚:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
2. 其他汇聚:除去可变汇聚剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;

(1) Variable aggregation
There is only one method for variable aggregation: collect, as its name indicates, it can collect the required elements in the Stream into a result container (such as Collection). First look at the definition of the most common collect method (there are other override methods):

<R> R collect(Supplier<R> supplier,
                 BiConsumer<R, ? super T> accumulator,
                 BiConsumer<R, R> combiner);

Let’s take a look at the meaning of these three parameters: Supplier supplier is a factory function used to generate a new container; BiConsumer accumulator is also a function used to add elements in the Stream to the result container; BiConsumer combiner is also a function , Used to merge multiple result containers in the intermediate state into one (used in concurrency).

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
   List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
           collect(() -> new ArrayList<Integer>(),
                   (list, item) -> list.add(item),
                   (list1, list2) -> list1.addAll(list2));

The above code is to filter out all nulls for a List whose element is of type Integer, and then collect the remaining elements into a new List. Take a closer look at the three parameters of the collect method. They are all functions in the form of lambdas.

第一个函数生成一个新的ArrayList实例;
第二个函数接受两个参数,第一个是前面生成的ArrayList对象,二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费完毕;
第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList全部加入到第一个中;

But the above collect method call is a bit too complicated, it doesn't matter! Let's take a look at another override version of the collect method, which depends on Collector .

<R, A> R collect(Collector<? super T, A, R> collector);

It's so refreshing! Boy, there is good news. Java 8 also provides us with Collector's tool class- Collectors , which has defined some static factory methods, such as Collectors.toCollection() to collect in Collection, Collectors.toList() to collect in List And Collectors.toSet() are collected into Set. There are many static methods like this, so I won't introduce them one by one here. You can go directly to the JavaDoc. Let's take a look at the simplification of the code using Collectors:

List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
               collect(Collectors.toList());

(2) Other aggregation
-reduce method: The reduce method is very general, and the count, sum, etc. introduced later can all be implemented using it. The reduce method has three override methods. This article introduces the two most commonly used, and the last one is left for the reader to learn. Let's look at the first form of the reduce method. The method is defined as follows:

Optional<T> reduce(BinaryOperator<T> accumulator);

Accepts a BinaryOperator type parameter, we can use lambda expressions when using it.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -&gt; sum + item).get());

You can see that the reduce method accepts a function. This function has two parameters. The first parameter is the return value of the last function execution (also called the intermediate result). The second parameter is the element in the stream. This function takes this Add the two values, and the sum will be assigned to the first parameter of the next execution of this function. It should be noted that the value of the first parameter is the first element of the Stream at the first execution, and the second parameter is the second element of the Stream . The return value type of this method is Optional, which is a feasible method for Java 8 to prevent NPE from appearing. A later article will introduce it in detail. Here, it is simply regarded as a container, which may contain 0 or 1 objects.
The visualized results of this process are shown in the figure:
Insert picture description here
There is also a very commonly used variant of the reduce method:

T reduce(T identity, BinaryOperator<T> accumulator);

This definition is basically the same as described above, but the difference is that it allows the user to provide an initial value for cyclic calculation, and if the Stream is empty, the value is directly returned. And this method will not return Optional, because there will be no null value. The following examples are given directly, and will not be explained.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));

– Count method: Get the number of elements in the Stream. It's relatively simple, so I will give an example directly without explaining it.

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().count());

Guess you like

Origin blog.csdn.net/xgb2018/article/details/109305076