[Java 8] Stream and method reference

Keywords: Java JavaSE

Chapter I Stream flow

1.1 INTRODUCTION

(1) Multi step traversal code of traditional collection

Almost all collections (such as Collection interface or Map interface) support direct or indirect traversal operations. When we need to operate on the elements in the Collection, in addition to the necessary addition, deletion and acquisition, the most typical is Collection traversal. For example:

import java.util.ArrayList;
import java.util.List;
public class Demo01ForEach {
public static void main(String[] args) {
	List<String> list = new ArrayList<>();
	list.add("zhang wuji");
	list.add("Zhou Zhiruo");
	list.add("Zhao Min");
	list.add("Zhang Qiang");
	list.add("Zhang Sanfeng");
	for (String name : list) {
		System.out.println(name);
	}
}

This is a very simple set traversal operation: print out each string in the set.
(2) Disadvantages of loop traversal

Lambda of Java 8 allows us to focus more on What rather than How. This has been compared with internal classes. Now, let's take a closer look at the above example code, and we can find that:

  • The syntax of the for loop is "how"
  • The loop body of the for loop is "what to do"

Why use loops? Because of traversal. But is loop the only way to traverse? Traversal refers to the processing of each element one by one, rather than a cycle from the first to the last. The former is the purpose and the latter is the way.
Imagine if you want to filter the elements in the collection:

  1. Filter set A into subset B according to condition 1;
  2. Then it is filtered into subset C according to condition 2.
    What can we do? Before Java 8, the practice may be as follows:
import java.util.ArrayList;
import java.util.List;
public class Demo02NormalFilter {
public static void main(String[] args) {
	List<String> list = new ArrayList<>();
	list.add("zhang wuji");
	list.add("Zhou Zhiruo");
	list.add("Zhao Min");
	list.add("Zhang Qiang");
	list.add("Zhang Sanfeng");
	List<String> zhangList = new ArrayList<>();
	for (String name : list) {
		if (name.startsWith("Zhang")) {
			zhangList.add(name);
		}
	}
	
	List<String> shortList = new ArrayList<>();
		for (String name : zhangList) {
			if (name.length() == 3) {
			shortList.add(name);
		}
	}
	for (String name : shortList) {
			System.out.println(name);
		}
	}
}

This code contains three loops, each with different functions:

  1. First, screen all people surnamed Zhang;
  2. Then screen people whose names have three words;
  3. Finally, print out the results.

Whenever we need to operate on the elements in the collection, we always need to cycle, cycle and recycle. Is this taken for granted? no Circulation is a way of doing things, not an end. On the other hand, using a linear loop means that it can only be traversed once. If you want to traverse again, you can only make
Start from scratch with another loop. How can Lambda's derivative Stream bring us more elegant writing?

(3) Better writing of Stream

import java.util.ArrayList;
import java.util.List;
public class Demo03StreamFilter {
public static void main(String[] args) {
	List<String> list = new ArrayList<>();
		list.add("zhang wuji");
		list.add("Zhou Zhiruo");
		list.add("Zhao Min");
		list.add("Zhang Qiang");
		list.add("Zhang Sanfeng");
		list.stream()
		.filter(s ‐> s.startsWith("Zhang"))
		.filter(s ‐> s.length() == 3)
		.forEach(System.out::println);
	}
}

Directly reading the literal meaning of the code can perfectly display the semantics of irrelevant logical methods: obtain stream, filter surname Zhang, filter length 3, and print one by one.
The code does not reflect the use of linear loops or any other algorithm for traversal. What we really want to do is better reflected in the code.

1.2 overview of streaming ideas

  • This figure shows multi-step operations such as filtering, mapping, skipping and counting. This is a processing scheme for collection elements, and the scheme is a "function model". Each box in the figure is a "flow", which can be converted from one flow model to another by calling the specified method. And the rightmost number
    3 is the final result.
  • The filter, map and skip here are all operating on the function model, and the collection elements are not really processed. Only when terminating method count
    When executing, the whole model will operate according to the specified policy. This is due to Lambda's delayed execution feature.

Note: "Stream stream" is actually a functional model of collection elements. It is neither a collection nor a data structure. It does not store any elements (or their address values).

A Stream is a queue of elements from a data source

  • Elements are specific types of objects that form a queue. Stream in Java does not store elements, but calculates on demand.
  • The source of the data stream. It can be a collection, an array, etc.

Unlike previous Collection operations, Stream operations have two basic features:

  • Pipelining: all intermediate operations will return the stream object itself. In this way, multiple operations can be connected in series into a pipe, just like the flow style. Doing so optimizes operations such as laziness and short circuiting.
  • Internal iteration: previously, traversal of the set was explicitly iterated outside the set through Iterator or enhanced for, which is called external iteration
    Generation. Stream provides a way of internal iteration, and the flow can directly call the traversal method.

When using a Stream, it usually includes three basic steps: obtaining a data source → data conversion → performing operations to obtain the desired results. Each time the original Stream object is converted, it does not change and returns a new Stream object (there can be multiple conversions), which allows its operations to be arranged like a chain and become a pipe.

1.3 get stream

java.util.stream.Stream is the most commonly used stream interface newly added to Java 8. (this is not a functional interface.)

Obtaining a stream is very simple. There are several common methods:

  • All Collection collections can obtain streams through the stream default method;
  • The static method of the Stream interface can obtain the Stream corresponding to the array.
package _20_Stream._1_establish Stream;

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

public class GetStream {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
// ...
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
// ...
        Stream<String> stream2 = set.stream();


        Vector<String> vector = new Vector<>();
        Stream<String> stream3 = vector.stream();


        //todo Map

        Map<String, String> map = new HashMap<>();
// ...
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();


        //todo gets the stream from the array
        String[] array = { "zhang wuji", "Zhang Cuishan", "Zhang Sanfeng", "Zhang Yiyuan" };
        Stream<String> stream = Stream.of(array);


    }
}

Note: the parameter of the of method is actually a variable parameter, so arrays are supported.

1.4 common methods

The operation of flow model is very rich. Here are some common API s. These methods can be divided into two types:

  • Deferred method: the return value type is still the method of the Stream interface itself, so chain call is supported. (except the termination method, all other methods are delay methods.)
  • Termination method: the return value type is no longer the method of the Stream interface itself, so chained calls like StringBuilder are no longer supported. In this section, the termination methods include count and forEach methods.
package _20_Stream._2_transformation;

import java.util.stream.Stream;

public class ForEach {
    public static void main(String[] args) {
        //The todo flow can only use the method once. After it is used up, the flow can no longer call it

        Stream<String> stream1 = Stream.of("zhang wuji", "Zhang Sanfeng", "Zhou Zhiruo");
        //todo 1. forEach traverses the data in the stream
        //Is a termination method with no return value
        stream1.forEach(name -> System.out.println(name));


        //todo 2.filter
        Stream<String> stream2 = Stream.of("zhang wuji", "Zhang Sanfeng", "Zhou Zhiruo");
        Stream<String> result = stream2.filter(s -> (s.startsWith("week")));
        result .forEach(System.out::println);

        //todo 3. Mapping: map
        Stream<String> original = Stream.of("10", "12", "18");
        Stream<Integer> res2 = original.map( x -> (Integer.parseInt(x)));
        res2.forEach(System.out::println);

        //todo 4. Count number: count termination method
        Stream<String> original2 = Stream.of("zhang wuji", "Zhang Sanfeng", "Zhou Zhiruo");
        Stream<String> result2 = original2.filter( s -> s.startsWith("Zhang"));
        System.out.println(result2.count()); // 2

        //todo 5. Take the previous delay methods
        Stream<String> original3 = Stream.of("zhang wuji", "Zhang Sanfeng", "Zhou Zhiruo");
        Stream<String> result3 = original3.limit(2);//"Zhang Wuji", "Zhang Sanfeng"
        System.out.println(result3.count()); //

        //todo 6. Skip the previous delay methods
        //When n is greater than or equal to the number of elements, an empty stream is returned
        Stream<String> original4 = Stream.of("zhang wuji", "Zhang Sanfeng", "Zhou Zhiruo");
        Stream<String> result4 = original4.skip(2);
        System.out.println(result4.count()); // 1


        //todo 7. Combination: concat the elements of two streams into the same stream  
        //Attention time static method
        Stream<String> streamA = Stream.of("zhang wuji");
        Stream<String> streamB = Stream.of("Zhang Cuishan");
        Stream<String> result5 = Stream.concat(streamA, streamB);
        result5.forEach(System.out::println); //Zhang Wuji, Zhang Cuishan

    }
}

Chapter II method reference

2.1 redundant Lambda scenario

Look at a simple functional interface to apply Lambda expressions:

@FunctionalInterface
public interface Printable {
	void print(String str);
}

In the Printable interface, the only abstract method print receives a string parameter to print and display it. The code to use Lambda is simple:

public class Demo01PrintSimple {
	private static void printString(Printable data) {
		data.print("Hello, World!");
	}
	public static void main(String[] args) {
		printString(s ‐> System.out.println(s));
	}
}

There is already an existing implementation, the println(String) method in the System.out object. Since all Lambda wants to do is call the println(String) method, why call it manually?

2.2 reference improvement code by method

public class Demo02PrintRef {
	private static void printString(Printable data) {
		data.print("Hello, World!");
	}
	public static void main(String[] args) {
		printString(System.out::println);
	}
}

Note the double colon:: notation, which is called "method reference", and the double colon is a new syntax.

2.3 method references

Double colon:: is a reference operator, and its expression is called a method reference. If the function scheme to be expressed by Lambda already exists in the implementation of a method, the method can be referenced as a substitute for Lambda through double colons.

Method reference essence: an abstract method that uses a method of an existing class to implement a functional interface

semantic analysis

For example, in the above example, there is an overloaded println(String) method in the System.out object, which is exactly what we need. Then, the functional interface parameters of printString method are completely equivalent by comparing the following two methods:

  • Lambda expression: S - > system. Out. Println (s);
  • Method reference writing method: System.out::println

The first semantics refers to: after getting the parameters, Lambda will pass them to the System.out.println method for processing.
The semantics of the second equivalent writing method is to directly let the println method in System.out replace Lambda. The execution effect of the two writing methods is exactly the same, while the writing method referenced by the second method reuses the existing scheme and is more concise.

Note: the parameter passed in Lambda must be the type that the method in the method reference can receive, otherwise an exception will be thrown

Derivation and omission

If Lambda is used, according to the principle of "derivable is omitted", there is no need to specify the parameter type or overload form - they will be derived automatically. If method reference is used, it can also be deduced according to the context.

Functional interface is the basis of Lambda, and method reference is Lambda's twin brother.

2.4 reference member method by object name

Posted by SteveMellor on Sun, 05 Dec 2021 08:52:08 -0800