Java8 New Features - StreamAPI (1)

1. Basic Concepts of Streams

1.1 What is a stream?

Stream is a new concept introduced in Java 8. It is used to process data in collections. For the time being, it can be understood as an advanced collection.

As we all know, the set operation is very troublesome. To filter and project the set, you need to write a lot of code, and the stream operates the set in the form of a statement. It is like a SQL statement. We only need to tell the stream what operations need to be performed on the set. It will do the operation automatically and give you the execution result, without the need for us to write the code ourselves.

Therefore, the set operation of the stream is transparent to us, we just issue a command to the stream and it will automatically give us the result we want. Since the operation process is completely handled by Java, it can choose the optimal method according to the current hardware environment, and we do not need to write complex and error-prone multi-threaded code.

1.2 Features of Streams

  1. We can only traverse once 
    . We can think of the stream as a pipeline. The source of the pipeline is our data source (a collection). The elements in the data source are sent to the pipeline in turn. We can perform various operations on the elements on the pipeline. Once elements go to the other end of the pipeline, they are "consumed" and we can no longer operate on the stream. Of course, we can get a new stream from the data source and traverse it again.

  2. To process the collection using the internal iteration method 
    , we need to write the processing code by hand, which is called external iteration. To process a stream, we just tell the stream what result we want, and the processing is done by the stream itself, which is called internal iteration.

1.3 Operation Types of Streams

There are two types of stream operations, intermediate operations and terminal operations.

  1. Intermediate operations 
    When the data in the data source is pipelined, all operations performed on the data in this process are called "intermediate operations". 
    Intermediate operations still return a stream object, so multiple intermediate operations can be chained together to form a pipeline.

  2. Terminal Operations 
    When all intermediate operations are completed, a terminal operation needs to be performed to take the data off the pipeline. 
    Terminal operations will return an execution result, which is the data you want.

1.4 The operation process of the stream

There are three steps to using streams:

  1. prepare a data source
  2. Execute intermediate operations 
    There can be multiple intermediate operations, and they can be chained together to form a pipeline.
  3. Execute 
    the terminal operation After the terminal operation is executed, the current flow ends, and you will get an execution result.

2. Use of Streams

2.1 Get the stream

Before using a stream, you first need to have a data source and obtain the stream object of the data source through some methods provided by StreamAPI. Data sources can take many forms:

  1. The collection 
    data source is more commonly used, and the stream object can be obtained by the stream() method:
List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();
  • 1
  • 2
  1. The array 
    obtains the stream object of the array through the static function stream() provided by the Arrays class:
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);
  • 1
  • 2
  1. value 
    directly turns several values ​​into stream objects:
Stream<String> stream = Stream.of("chaimm","peter","john");
  • 1
  1. File 
    try(Stream lines = Files.lines(Paths.get("file path name"), Charset.defaultCharset())){ 
    //Some operations can be done on lines 
    }catch(IOException e){ 

    PS: Java7 simplifies IO operation, put the open IO operation in the parentheses after the try to omit the code to close the IO.

2.2 Filter filter

The filter function receives a Lambda expression as a parameter, and the expression returns a boolean. During the execution process, the stream sends the elements to the filter one by one, and filters out the elements whose execution result is true. 
For example, to filter out all students:

List<Person> result = list.stream()
                    .filter(Person::isStudent)
                    .collect(toList());
  • 1
  • 2
  • 3

2.3 Deduplication distinct

Remove duplicate results:

List<Person> result = list.stream()
                    .distinct()
                    .collect(toList());
  • 1
  • 2
  • 3

2.4 Interception

Intercept the first N elements of the stream:

List<Person> result = list.stream()
                    .limit(3)
                    .collect(toList());
  • 1
  • 2
  • 3

2.5 Skip

Skip the first n elements of the stream:

List<Person> result = list.stream()
                    .skip(3)
                    .collect(toList());
  • 1
  • 2
  • 3

2.6 Mapping

Executes a function on each element in the stream, causing the element to be converted to another type of output. The stream will feed each element to the map function, execute the lambda expression in the map, and finally store the execution result in a new stream. 
For example, to get everyone's name (in fact, the Perosn type is converted to a String type):

List<Person> result = list.stream()
                    .map(Person::getName)
                    .collect(toList());
  • 1
  • 2
  • 3

2.7 Merging multiple streams

Example: List the different words in the List. The List collection is as follows:

List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");
  • 1
  • 2
  • 3
  • 4

The idea is as follows:

  • First turn the list into a stream:
list.stream();
  • 1
  • Split words by spaces:
list.stream()
            .map(line->line.split(" "));
  • 1
  • 2

After dividing the words, each element becomes a String[] array.

  • Turn each String[] into a stream:
list.stream()
            .map(line->line.split(" "))
            .map(Arrays::stream)
  • 1
  • 2
  • 3

At this point, a large stream contains small streams, and we need to combine these small streams into one stream.

  • Merge small streams into one large stream: 
    replace the previous map with flagmap
list.stream()
            .map(line->line.split(" "))
            .flagmap(Arrays::stream)
  • 1
  • 2
  • 3
  • deduplication
list.stream()
            .map(line->line.split(" "))
            .flagmap(Arrays::stream)
            .distinct()
            .collect(toList());
  • 1
  • 2
  • 3
  • 4
  • 5

2.8 Whether to match any element: anyMatch

anyMatch is used to determine whether there is at least one element in the stream that satisfies the specified condition. This condition is passed to anyMatch through a Lambda expression, and the execution result is of type boolean. 
For example, to determine whether there are students in the list:

boolean result = list.stream()
            .anyMatch(Person::isStudent);
  • 1
  • 2

2.9 Whether to match all elements: allMatch

allMatch is used to judge whether all elements in the stream meet the specified condition. This condition is passed to anyMatch through a Lambda expression, and the execution result is of type boolean. 
For example, to determine if everyone is a student:

boolean result = list.stream()
            .allMatch(Person::isStudent);
  • 1
  • 2

2.10 If all elements are not matched: noneMatch

noneMatch is the exact opposite of allMatch, it is used to determine whether all elements in the stream do not meet the specified conditions:

boolean result = list.stream()
            .noneMatch(Person::isStudent);
  • 1
  • 2

2.11 Get any element findAny

findAny can randomly select an element from the stream, and it returns an element of type Optional.

Optional<Person> person = list.stream()
                                    .findAny();
  • 1
  • 2

Introduction to Optional

Optional is a newly added container in Java8. This container only stores 1 or 0 elements. It is used to prevent NullpointException. It provides the following methods:

  • isPresent() 
    determines whether there is a value in the container.
  • ifPresent(Consume lambda) 
    容器若不为空则执行括号中的Lambda表达式。
  • T get() 
    获取容器中的元素,若容器为空则抛出NoSuchElement异常。
  • T orElse(T other) 
    获取容器中的元素,若容器为空则返回括号中的默认值。

2.12 获取第一个元素findFirst

Optional<Person> person = list.stream()
                                    .findFirst();
  • 1
  • 2

2.13 归约

归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出。

在流中,reduce函数能实现归约。 
reduce函数接收两个参数:

  • 初始值
  • 进行归约操作的Lambda表达式

2.13.1 元素求和:自定义Lambda表达式实现求和

例:计算所有人的年龄总和

int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
  • 1

reduce的第一个参数表示初试值为0; 
reduce的第二个参数为需要进行的归约操作,它接收一个拥有两个参数的Lambda表达式,reduce会把流中的元素两两输给Lambda表达式,最后将计算出累加之和。

2.13.2 元素求和:使用Integer.sum函数求和

上面的方法中我们自己定义了Lambda表达式实现求和运算,如果当前流的元素为数值类型,那么可以使用Integer提供了sum函数代替自定义的Lambda表达式,如:

int age = list.stream().reduce(0, Integer::sum);
  • 1

Integer类还提供了min、max等一系列数值操作,当流中元素为数值类型时可以直接使用。

2.14 数值流的使用

采用reduce进行数值操作会涉及到基本数值类型和引用数值类型之间的装箱、拆箱操作,因此效率较低。 
当流操作为纯数值操作时,使用数值流能获得较高的效率。

2.14.1 将普通流转换成数值流

StreamAPI提供了三种数值流:IntStream、DoubleStream、LongStream,也提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。 
如,将Person中的age转换成数值流:

IntStream stream = list.stream()
                            .mapToInt(Person::getAge);
  • 1
  • 2

2.14.2 数值计算

每种数值流都提供了数值计算函数,如max、min、sum等。 
如,找出最大的年龄:

OptionalInt maxAge = list.stream()
                                .mapToInt(Person::getAge)
                                .max();
  • 1
  • 2
  • 3

由于数值流可能为空,并且给空的数值流计算最大值是没有意义的,因此max函数返回OptionalInt,它是Optional的一个子类,能够判断流是否为空,并对流为空的情况作相应的处理。 
此外,mapToInt、mapToDouble、mapToLong进行数值操作后的返回结果分别为:OptionalInt、OptionalDouble、OptionalLong

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324507118&siteId=291194637