Java 8 Stream: Mastering the Essence of Streaming Programming Title

Foreword

This article is from a blogger I like. Mainly used for learning and recording purposes. If you have any questions, please message me privately. Thanks!

Master Stream flow

Two weeks ago, a reader strongly requested me to write an article about Java Stream. I said that there are already many articles on the market. Guess what he said: “I just want to read what you write!” Look. Look, what a pale love. Then just “do it with difficulty” and write an article, hehe.

img

Judging from the word “Stream” alone, it seems to have some relationship with InputStream and OutputStream under the java.io package. Actually, it doesn’t matter. The new Stream in Java 8 is to liberate programmers’ productivity when operating collections. A large part of the reason for this liberation can be attributed to the emergence of Lambda expressions – which greatly improves programming efficiency and program availability. Readability.

What exactly is Stream?

Stream is like an advanced iterator, but it can only be traversed once, just like a river of spring water flowing eastward; during the flow process, some operations are performed on the elements in the stream, such as “filter out strings with a length greater than 10”, “Get the first letter of each string” etc.

To operate a stream, you first need to have a data source, which can be an array or a collection. Each operation will return a new stream object to facilitate chain operations, but the original stream object will remain unchanged.

Stream operations can be divided into two types:

1) There can be multiple intermediate operations. Each time a new stream is returned, chain operations can be performed.

2) There can only be one terminal operation. After each execution, the stream will be used up and the next operation cannot be performed, so it can only be placed at the end.

Let’s give an example.

List<String> list = new ArrayList<>();
list.add("Come on Wuhan");
list.add("Come on China");
list.add("Come on world");
list.add("Come on world");
?
long count = list.stream().distinct().count();
System.out.println(count);

The distinct() method is an intermediate operation (deduplication) that returns a new stream (with no common elements).

Stream<T> distinct();

The count() method is a terminal operation that returns the number of elements in the stream.

long count();

Intermediate operations will not be executed immediately. Only when the terminal operation is performed, the stream will actually start to be traversed for mapping, filtering, etc. In layman’s terms, if you perform multiple operations in one traversal, the performance will be greatly improved.

That’s all for the theoretical part, let’s go directly to the practical part.

01. Create a stream

If it is an array, you can use Arrays.stream() or Stream.of() to create a stream; if it is a collection, you can use stream()< directly /code> method creates the stream because this method has been added to the Collection interface.

public class CreateStreamDemo {
    public static void main(String[] args) {
        String[] arr = new String[]{"Come on Wuhan", "Come on China", "Come on the world"};
        Stream<String> stream = Arrays.stream(arr);
?
        stream = Stream.of("Come on Wuhan", "Come on China", "Come on the world");
?
        List<String> list = new ArrayList<>();
        list.add("Come on Wuhan");
        list.add("Come on China");
        list.add("Come on world");
        stream = list.stream();
    }
}

If you look at the Stream source code, you will find that the of() method actually calls the Arrays.stream() method internally.

public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

In addition, the collection can also call the parallelStream() method to create concurrent streams. The default is the ForkJoinPool.commonPool() thread pool.

List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();

02. Operation flow

The Stream class provides many useful methods for operating streams. Let me select some commonly used ones to introduce to you.

1) Filtering

The filter() method can filter out the elements we want from the stream.

public class FilterStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Jay Chou");
        list.add("Wang Leehom");
        list.add("Tao Zhe");
        list.add("JJ Lin");
        Stream<String> stream = list.stream().filter(element -> element.contains("王"));
        stream.forEach(System.out::println);
    }
}

The filter() method receives a parameter of type Predicate (a new functional interface in Java 8, which accepts an input parameter and returns a Boolean result). Therefore, we can directly express a Lambda The formula is passed to this method, for example, element -> element.contains("王") is to filter out the string with "王".

The forEach() method receives a parameter of type Consumer (a new functional interface added in Java 8, which accepts an input parameter and has no return operation). Class name:: Method name is a new syntax introduced in Java 8. System.out returns the PrintStream class. You should know that the println method is used for printing.

stream.forEach(System.out::println); is equivalent to printing in a for loop, similar to the following code:

for (String s : strs) {
    System.out.println(s);
}

Obviously, one line of code looks cleaner. Let’s take a look at the output of the program:

Wang Leehom
2) Mapping

If you want to convert elements in a stream into elements in a new stream through some operation, you can use the map() method.

public class MapStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Jay Chou");
        list.add("Wang Leehom");
        list.add("Tao Zhe");
        list.add("JJ Lin");
        Stream<Integer> stream = list.stream().map(String::length);
        stream.forEach(System.out::println);
    }
}

The map() method receives a parameter of type Function (a new functional interface in Java 8, which accepts an input parameter T and returns a result R). At this time, the parameter is the length of the String class. Method, that is, convert the stream of Stream into a stream of Stream.

The results output by the program are as follows:

3
3
2
3
3) Match

The Stream class provides three methods for element matching, which are:

  • anyMatch(), returns true as long as an element matches the passed condition.

  • allMatch(), if only one element does not match the passed-in condition, it returns false; if all elements match, it returns true.

  • noneMatch(), as long as one element matches the incoming condition, it returns false; if none matches, it returns true.

public class MatchStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Jay Chou");
        list.add("Wang Leehom");
        list.add("Tao Zhe");
        list.add("JJ Lin");

        boolean anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));
        boolean allMatchFlag = list.stream().allMatch(element -> element.length() > 1);
        boolean noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("深"));
        System.out.println(anyMatchFlag);
        System.out.println(allMatchFlag);
        System.out.println(noneMatchFlag);
    }
}

Because "Wang Leehom" starts with the word "王", anyMatchFlag should be true; because the string lengths of "Jay Chou", "Wang Leehom", "Tao Zhe", and "JJ Lin" are all greater than 1, so allMatchFlag is true; because 4 characters The end of the string is not "sun", so noneMatchFlag is true.

The results output by the program are as follows:

true
true
true
4) Combination

The main function of the reduce() method is to combine the elements in the Stream. It has two uses:

  • Optional reduce(BinaryOperator accumulator)

There is no starting value and only one parameter, which is the operation rule. In this case, Optional is returned.

  • T reduce(T identity, BinaryOperator accumulator)

There is a starting value, operation rules, and two parameters. The returned type is the same as the starting value type.

Consider the following example.

public class ReduceStreamDemo {
    public static void main(String[] args) {
        Integer[] ints = {0, 1, 2, 3};
        List<Integer> list = Arrays.asList(ints);

        Optional<Integer> optional = list.stream().reduce((a, b) -> a + b);
        Optional<Integer> optional1 = list.stream().reduce(Integer::sum);
        System.out.println(optional.orElse(0));
        System.out.println(optional1.orElse(0));

        int reduce = list.stream().reduce(6, (a, b) -> a + b);
        System.out.println(reduce);
        int reduce1 = list.stream().reduce(6, Integer::sum);
        System.out.println(reduce1);
    }
}

The operation rule can be a Lambda expression (such as (a, b) -> a + b) or a class name::method name (such as Integer::sum ).

The result of running the program is as follows:

6
6
12
12

When 0, 1, 2, 3 are added without a starting value, the result is 6; when there is a starting value of 6, the result is 12.

03. Conversion flow

Since a collection or array can be converted into a stream, there should also be a corresponding method to convert the stream back - the collect() method meets this need.

public class CollectStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Jay Chou");
        list.add("Wang Leehom");
        list.add("Tao Zhe");
        list.add("JJ Lin");

        String[] strArray = list.stream().toArray(String[]::new);
        System.out.println(Arrays.toString(strArray));

        List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());
        List<String> list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
        System.out.println(list1);
        System.out.println(list2);

        String str = list.stream().collect(Collectors.joining(", ")).toString();
        System.out.println(str);
    }
}

The toArray() method can convert a stream into an array. You may be curious about String[]::new. What is it? Let’s take a look at the source code of the toArray() method.

<A> A[] toArray(IntFunction<A[]> generator);

In other words, String[]::new is an IntFunction, a function that can generate the required new array. You can decompile the bytecode to see what it is:

String[] strArray = (String[])list.stream().toArray((x$0) -> {
    return new String[x$0];
});
System.out.println(Arrays.toString(strArray));

That is equivalent to returning a string array of specified length.

When we need to convert a collection into another collection according to certain rules, we can use the map() method and the collect() method together.

List<Integer> list1 = list.stream().map(String::length).collect(Collectors.toList());

After creating the stream of the collection through the stream() method, it is mapped to a new stream of string length through map(String:length), and finally through The collect() method converts it into a new collection.

Collectors is a collector tool class with a series of built-in collector implementations. For example, the toList() method collects elements into a new java.util.List ; For example, the toCollection() method collects elements into a new java.util.ArrayList; for example, the joining() method collects elements into Collected into a string that can be specified using delimiters.

Let’s take a look at the output of the program:

[Jay Chou, Leehom Wang, Tao Zhe, JJ Lin]
[3, 3, 2, 3]
[Jay Chou, Wang Leehom, Tao Zhe, JJ Lin]
Jay Chou, Wang Leehom, Tao Zhe, JJ Lin
syntaxbug.com © 2021 All Rights Reserved.