New features of jdk8

primary coverage

Learning objectives

Chapter I functional interface

1.1 concept

Functional interface in Java refers to an interface with only one abstract method.

Functional interface, that is, the interface suitable for functional programming scenarios. The embodiment of functional programming in Java is lambda, so functional interface is the interface that can be used by lambda. Only by ensuring that there is and only one abstract method in the interface can lambda in Java be deduced smoothly.

1.2 format

Just ensure that there is and only one abstract method in the interface:

修饰符 interface 接口名称 {
	public abstract 返回值类型 方法名称(可选参数信息);
	// 其他非抽象方法内容
}

Since the public abstract of the abstract method in the interface can be omitted, it is very simple to define a functional interface:

public interface MyFunctionalInterface {	
	void myMethod();
}

1.3 @ functionalinterface annotation

Similar to the @ override annotation, Java 8 specifically introduces a new annotation for functional interfaces: @ functional interface. This annotation can be used for the definition of an interface:

@FunctionalInterface
public interface MyFunctionalInterface {
	void myMethod();
}

Once the annotation is used to define the interface, the compiler will force to check whether the interface does have and only has one abstract method, otherwise an error will be reported. It should be noted that even if the annotation is not used, as long as the definition of the functional interface is met, it is still a functional interface, which is the same.

1.4 user defined functional interface

For the newly defined myfunctionalinterface functional interface, the typical usage scenario is as a method parameter:

public class Demo09FunctionalInterface {	
	// 使用自定义的函数式接口作为方法参数
	private static void doSomething(MyFunctionalInterface inter) {
		inter.myMethod(); // 调用自定义的函数式接口方法
	}

	public static void main(String[] args) {
		// 调用使用函数式接口的方法
		doSomething(() -> System.out.println("Lambda执行啦!"));
	}
}

Chapter II functional programming

On the basis of taking into account the object-oriented characteristics, Java language opens the door to functional programming for developers through lambda expressions and method references. Let's make a preliminary study.

2.1 delayed execution of lambda

After the code of some scenarios is executed, the results may not be used, resulting in performance waste. Lambda expressions are delayed, which can be used as a solution to improve performance.

Log cases of performance waste

Note: logs can help us quickly locate problems and record the situation during the operation of the program, so as to facilitate the monitoring and optimization of the project.

A typical scenario is to conditionally use parameters. For example, after splicing log messages, print them when the conditions are met:

public class Demo01Logger {
	private static void log(int level,String msg) {
    	if (level == 1) {
      		System.out.println(msg);
    	}
	}

	public static void main(String[] args) {
    	String msgA = "Hello";
    	String msgB = "World";
    	String msgC = "Java";

    	log(1,msgA + msgB + msgC);
	}
}

There is a problem with this Code: whether the level meets the requirements or not, as the second parameter of the log method, the three strings must be spliced and passed into the method first, and then the level judgment will be made. If the level does not meet the requirements, the string splicing operation will be done in vain, resulting in performance waste.

Experience lambda's better writing

Using lambda requires a functional interface:

@FunctionalInterface
public interface MessageBuilder {  
	String buildMessage();
}

Then the log method is modified:

public class Demo02LoggerLambda {
	private static void log(int level,MessageBuilder builder) {
    	if (level == 1) {
      		System.out.println(builder.buildMessage());
    	}
	}

	public static void main(String[] args) {
    	String msgA = "Hello";
    	String msgB = "World";
    	String msgC = "Java";

    	log(1,() -> msgA + msgB + msgC );
	}
}

In this way, only when the level meets the requirements will the three strings be spliced; Otherwise, the three strings will not be spliced.

Proving lambda's delay

The following code can be verified by the results:

public class Demo03LoggerDelay {
	private static void log(int level,MessageBuilder builder) {
    	if (level == 1) {
      		System.out.println(builder.buildMessage());
    	}
	}

	public static void main(String[] args) {
    	String msgA = "Hello";
    	String msgB = "World";
    	String msgC = "Java";

    	log(2,() -> {
        	System.out.println("Lambda执行!");
        	return msgA + msgB + msgC;
    	});
	}
}

It can be seen from the results that lambda will not execute if the level requirements are not met. So as to achieve the effect of saving performance.

2.2 use lambda as parameter and return value

Regardless of the implementation principle, lambda expressions in Java can be regarded as a substitute for anonymous inner classes. If the parameter of a method is a functional interface type, you can use a lambda expression instead. Using a lambda expression as a method parameter is actually using a functional interface as a method parameter.

For example, Java The lang. runnable interface is a functional interface. Assuming that a startthread method uses this interface as a parameter, you can use lambda to pass parameters. In fact, this situation is not fundamentally different from the construction method parameter of thread class, which is runnable.

public class Demo04Runnable {
	private static void startThread(Runnable task) {
  		new Thread(task).start();
	}

	public static void main(String[] args) {
  		startThread(() -> System.out.println("线程任务执行!"));
	}
}

Similarly, if the return value type of a method is a functional interface, you can directly return a lambda expression. When you need a method to get a Java util. When an object of comparator interface type is used as a sorter, you can call this method to obtain.

import java.util.Arrays;
import java.util.Comparator;

public class Demo06Comparator {
	private static Comparator<String> newComparator() {
  		return (a,b) -> b.length() - a.length();
	}

	public static void main(String[] args) {
    	String[] array = { "abc","ab","abcd" };
    	System.out.println(Arrays.toString(array));
    	Arrays.sort(array,newComparator());
    	System.out.println(Arrays.toString(array));
	}
}

Directly return a lambda expression.

Chapter III common functional interfaces

JDK provides a large number of commonly used functional interfaces to enrich the typical usage scenarios of lambda, mainly in Java util. Function package. The following are the simplest interfaces and use examples.

3.1 supplier interface

java. util. function. The supplier < T > interface contains only one parameterless method: t get(). Used to get object data of the type specified by a generic parameter. Because this is a functional interface, it means that the corresponding lambda expression needs to "provide" an object data conforming to the generic type.

import java.util.function.supplier;

public class Demo08supplier {
	private static String getString(supplier<String> function) {
  		return function.get();
	}

	public static void main(String[] args) {
    	String msgA = "Hello";
    	String msgB = "World";
    	System.out.println(getString(() -> msgA + msgB));
	}
}

Exercise: maximizing array elements

subject

Using the supplier interface as the method parameter type, find the maximum value in the int array through the lambda expression. Tip: for the generic type of interface, please use Java Lang. integer class.

answer

public class Demo02Test {
	//定一个方法,方法的参数传递supplier,泛型使用Integer
	public static int getMax(supplier<Integer> sup){
    	return sup.get();
	}

	public static void main(String[] args) {
    	int arr[] = {2,3,4,52,333,23};

    	//调用getMax方法,参数传递Lambda
    	int maxNum = getMax(()->{
       	//计算数组的最大值
       	int max = arr[0];
       	for(int i : arr){
        	if(i>max){
            	max = i;
           	}
       	}
       	return max;
    	});
    	System.out.println(maxNum);
	}
}

3.3 consumer interface

java. util. function. The consumer < T > interface is just opposite to the supplier interface. Instead of producing a data, it consumes a data, and its data type is determined by generics.

Abstract method: accept

The consumer interface contains the abstract method void accept (T), which means to consume data of a specified generic type. Basic usage, such as:

import java.util.function.Consumer;

public class Demo09Consumer {
	private static void consumeString(Consumer<String> function) {
  		function.accept("Hello");
	}

	public static void main(String[] args) {
    	consumeString(s -> System.out.println(s));
	}
}

Of course, a better way to write is to use method references.

Default method: andthen

If the parameters and return values of a method are all of consumer type, the effect can be achieved: when consuming data, first do an operation, and then do an operation to realize combination. This method is the default method andthen in the consumer interface. The following is the source code of JDK:

default Consumer<T> andThen(Consumer<? super T> after) {
	Objects.requireNonNull(after);
	return (T t) -> { accept(t); after.accept(t); };
}

Note: Java util. The requirenonnull static method of objects will actively throw a NullPointerException exception when the parameter is null. This eliminates the trouble of repeatedly writing if statements and throwing null pointer exceptions.

To realize composition, you need two or more lambda expressions, and the semantics of andthen is "step by step". For example, when two steps are combined:

import java.util.function.Consumer;

public class Demo10ConsumerAndThen {
	private static void consumeString(Consumer<String> one,Consumer<String> two) {
  		one.andThen(two).accept("Hello");
	}

	public static void main(String[] args) {
    	consumeString(
        	s -> System.out.println(s.toUpperCase()),s -> System.out.println(s.toLowerCase()));
	}
}

The run result will first print hello in full uppercase and then hello in full lowercase. Of course, more steps can be combined through chain writing.

3.4 exercise: formatting and printing information

subject

There are multiple pieces of information in the following string array. Please follow the format "Name: XX. Gender: XX." Print the information in a format. It is required to take the action of printing name as the lambda instance of the first consumer interface and the action of printing gender as the lambda instance of the second consumer interface, and "splice" the two consumer interfaces together in order.

public static void main(String[] args) {
	String[] array = { "迪丽热巴,女","古力娜扎,"马尔扎哈,男" };
}

answer

import java.util.function.Consumer;

public class DemoConsumer {
	public static void main(String[] args) {
    	String[] array = { "迪丽热巴,男" };
    	printInfo(s -> System.out.print("姓名:" + s.split(",")[0]),s -> System.out.println("。性别:" + s.split(",")[1] + "。"),array);
	}

	private static void printInfo(Consumer<String> one,Consumer<String> two,String[] array) {
    	for (String info : array) {
        	one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
    	}
	}
}

3.5 predict interface

Sometimes we need to judge some type of data to get a Boolean value result. You can use Java util. function. Predict < T > interface.

Abstract method: Test

The predict interface contains an abstract method: Boolean test (T). Scenarios for condition judgment:

import java.util.function.Predicate;

public class Demo15PredicateTest {
	private static void method(Predicate<String> predicate) {
    	boolean veryLong = predicate.test("HelloWorld");
    	System.out.println("字符串很长吗:" + veryLong);
	}

	public static void main(String[] args) {
    	method(s -> s.length() > 5);
	}
}

The criterion of condition judgment is the incoming lambda expression logic. Here, as long as the string length is greater than 5, it is considered to be very long.

Default method: and

Since it is conditional judgment, there will be three common logical relationships: and, or and non. The default method and can be used when two predicate conditions are connected with and logic to achieve the effect of "and". Its JDK source code is:

default Predicate<T> and(Predicate<? super T> other) {
	Objects.requireNonNull(other);
	return (t) -> test(t) && other.test(t);
}

If you want to judge that a string must contain both uppercase "H" and uppercase "W", then:

import java.util.function.Predicate;

public class Demo16PredicateAnd {
	private static void method(Predicate<String> one,Predicate<String> two) {
    	boolean isValid = one.and(two).test("Helloworld");
    	System.out.println("字符串符合要求吗:" + isValid);
	}

	public static void main(String[] args) {
    	method(s -> s.contains("H"),s -> s.contains("W"));
	}
}

Default method: or

Similar to the and of and, the default method or implements the or in the logical relationship. The JDK source code is:

default Predicate<T> or(Predicate<? super T> other) {
	Objects.requireNonNull(other);
	return (t) -> test(t) || other.test(t);
}

If you want to realize the logic "string contains uppercase h or uppercase W", the code only needs to change "and" to "or" name, and everything else remains the same:

import java.util.function.Predicate;

public class Demo16PredicateAnd {
	private static void method(Predicate<String> one,Predicate<String> two) {
    	boolean isValid = one.or(two).test("Helloworld");
    	System.out.println("字符串符合要求吗:" + isValid);
	}

	public static void main(String[] args) {
    	method(s -> s.contains("H"),s -> s.contains("W"));
	}
}

Default method: negate

The "and" and "or" have been understood, and the remaining "non" (negative) will be simple. The JDK source code of the default method negate is:

default Predicate<T> negate() {
	return (t) -> !test(t);
}

It is easy to see from the implementation that it is to "!" the Boolean value of the result after executing the test method Take it instead. Be sure to call the negate method before the test method invocation, just like and and or methods:

import java.util.function.Predicate;

public class Demo17PredicateNegate {
	private static void method(Predicate<String> predicate) {
    	boolean veryLong = predicate.negate().test("HelloWorld");
    	System.out.println("字符串很长吗:" + veryLong);
	}

	public static void main(String[] args) {
  		method(s -> s.length() < 5);
	}
}

3.6 exercise: collection information filtering

subject

There are multiple "name + gender" information in the array as follows. Please filter the qualified strings into the set ArrayList through the assembly of the predicate interface. Two conditions need to be met at the same time:

answer

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class DemoPredicate {
	public static void main(String[] args) {
    	String[] array = { "迪丽热巴,女" };
    	List<String> list = filter(array,s -> "女".equals(s.split(",")[1]),s -> s.split(",")[0].length() == 4);
    	System.out.println(list);
	}

	private static List<String> filter(String[] array,Predicate<String> one,Predicate<String> two) {
    	List<String> list = new ArrayList<>();
    	for (String info : array) {
        	if (one.and(two).test(info)) {
            	list.add(info);
        	}
    	}
    	return list;
	}
}

3.7 function interface

java. util. function. The function < T, R > interface is used to obtain another type of data from one type of data. The former is called pre condition and the latter is called post condition.

Abstract method: apply

The main abstract method in the function interface is r apply (t t), which obtains the result of type R according to the parameters of type T.

Use scenarios such as converting string type to integer type.

import java.util.function.Function;

public class Demo11FunctionApply {
	private static void method(Function<String,Integer> function) {
    	int num = function.apply("10");
    	System.out.println(num + 20);
	}

	public static void main(String[] args) {
    	method(s -> Integer.parseInt(s));
	}
}

Of course, it's best to write through method reference.

Default method: andthen

There is a default andthen method in the function interface, which is used for combination operations. JDK source code, such as:

default <V> Function<T,V> andThen(Function<? super R,? extends V> after) {
	Objects.requireNonNull(after);
	return (T t) -> after.apply(apply(t));
}

This method is also used in the "do what first, then do what" scenario, which is similar to andthen in consumer:

import java.util.function.Function;

public class Demo12FunctionAndThen {
	private static void method(Function<String,Integer> one,Function<Integer,Integer> two) {
    	int num = one.andThen(two).apply("10");
    	System.out.println(num + 20);
	}

	public static void main(String[] args) {
    	method(str->Integer.parseInt(str)+10,i -> i *= 10);
	}
}

The first operation is to parse the string into an int number, and the second operation is to multiply by 10. The two operations are combined in sequence by andthen.

3.8 exercise: splicing custom function models

subject

Please use function to splice function models. Multiple function operations to be performed in order are:

String str = "赵丽颖,20";

answer

import java.util.function.Function;

public class DemoFunction {
	public static void main(String[] args) {
    	String str = "赵丽颖,20";
    	int age = getAgeNum(str,")[1],s ->Integer.parseInt(s),n -> n += 100);
    	System.out.println(age);
	}

	private static int getAgeNum(String str,Function<String,String> one,Integer> two,Integer> three) {
    	return one.andThen(two).andThen(three).apply(str);
	}
}

When it comes to streams, it's easy to think of I / O streams. In fact, who stipulates that "streams" must be "IO streams"? In Java 8, thanks to the functional programming brought by lambda, a new stream concept is introduced to solve the existing disadvantages of the existing collection class library.

4.1 introduction

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, the most typical is collection traversal in addition to the necessary addition, deletion and acquisition. 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("张无忌");
    	list.add("周芷若");
    	list.add("赵敏");
    	list.add("张强");
    	list.add("张三丰");
    	for (String name : list) {
      		System.out.println(name);
    	}
	}  
}

This is a very simple set traversal operation: print out each string in the set.

Disadvantages of loop traversal

Lambda in Java 8 allows us to focus more on what rather than how. This has been explained in combination with internal classes. Now, let's carefully experience the above example code and find:

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:

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("张无忌");
    	list.add("周芷若");
    	list.add("赵敏");
    	list.add("张强");
    	list.add("张三丰");

    	List<String> zhangList = new ArrayList<>();
    	for (String name : list) {
        	if (name.startsWith("张")) {
          		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:

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 iterate again, you can only start from scratch with another loop.

Better writing of stream

Let's take a look at what elegance is with the stream API of Java 8:

import java.util.ArrayList;
import java.util.List;

public class Demo03StreamFilter {
	public static void main(String[] args) {
    	List<String> list = new ArrayList<>();
    	list.add("张无忌");
    	list.add("周芷若");
    	list.add("赵敏");
    	list.add("张强");
    	list.add("张三丰");

    	list.stream()
      		.filter(s -> s.startsWith("张"))
        	.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.

4.2 overview of streaming ideas

Note: Please temporarily forget the inherent impression of traditional IO streams!

On the whole, the flow idea is similar to the "production line" in the factory workshop.

When multiple elements need to be operated (especially multi-step operation), considering the performance and convenience, we should first spell out a "model" step scheme, and then execute it according to the scheme.

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. The number 3 on the far right 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 the termination method count is executed, the whole model will execute the operation according to the specified policy. This is due to lambda's delayed execution feature.

A stream is a queue of elements from a data source

Unlike previous collection operations, stream operations have two basic features:

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.

4.3 get stream

java. util. stream. Stream < T > 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:

Get stream from collection

First, Java util. The default method stream is added to the collection interface to obtain the stream, so all its implementation classes can obtain the stream.

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

public class Demo04GetStream {
	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();
	}
}

Get stream from map

java. util. Map interface is not a sub interface of collection, and its K-V data structure does not conform to the single characteristics of flow elements. Therefore, to obtain the corresponding flow, you need to divide it into key, value or entry:

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Demo05GetStream {
	public static void main(String[] args) {
    	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();
	}
}

Get stream from array

If you are using an array instead of a collection or mapping, since it is impossible to add a default method to the array object, the stream interface provides a static method of, which is very simple to use:

import java.util.stream.Stream;

public class Demo06GetStream {
	public static void main(String[] args) {
    	String[] array = { "张无忌","张翠山","张三丰","张一元" };
    	Stream<String> stream = Stream.of(array);
	}
}

4.4 common methods

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

Process one by one: foreach

Although the method name is foreach, it is different from the nickname "for each" in the for loop.

void forEach(Consumer<? super T> action);

This method receives a consumer interface function and will give each stream element to this function for processing.

Review consumer interface

java.util.function.Consumer<T>接口是一个消费型接口。
Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。

Basic usage:

import java.util.stream.Stream;

public class Demo12StreamForEach {
	public static void main(String[] args) {
    	Stream<String> stream = Stream.of("张无忌","周芷若");
    	stream.forEach(name-> System.out.println(name));
	}
}

Filter: filter

You can convert a stream into another subset stream through the filter method. Method signature:

Stream<T> filter(Predicate<? super T> predicate);

The interface receives a predicate functional interface parameter (which can be a lambda or method reference) as a filter condition.

Review the predict interface

We have studied Java util. stream. Predict functional interface, in which the only abstract method is:

boolean test(T t);

This method will produce a Boolean value that represents whether the specified conditions are met. If the result is true, the filter method of the stream stream will retain elements; If the result is false, the filter method discards the element.

Basic use

The basic code used by the filter method in the stream stream is as follows:

import java.util.stream.Stream;

public class Demo07StreamFilter {
	public static void main(String[] args) {
    	Stream<String> original = Stream.of("张无忌","周芷若");
    	Stream<String> result = original.filter(s -> s.startsWith("张"));
	}
}

Here, the filter condition is specified through lambda expression: must be Zhang.

Mapping: Map

If you need to map elements in a stream to another stream, you can use the map method. Method signature:

<R> Stream<R> map(Function<? super T,? extends R> mapper);

This interface requires a function type interface parameter, which can convert T-type data in the current stream into another R-type stream.

Review function interface

We have studied Java util. stream. Function functional interface, in which the only abstract method is:

R apply(T t);

This can convert a T type to an R type, and the action of this conversion is called "mapping".

Basic use

The basic code used by the map method in the stream stream is as follows:

import java.util.stream.Stream;

public class Demo08StreamMap {
	public static void main(String[] args) {
    	Stream<String> original = Stream.of("10","12","18");
    	Stream<Integer> result = original.map(str->Integer.parseInt(str));
	}
}

In this code, the parameters of the map method convert the string type into the int type through the method reference (and are automatically boxed into an integer class object).

Number of Statistics: count

Just like the size method in the old collection, the flow provides the count method to count the number of elements:

long count();

This method returns a long value representing the number of elements (it is no longer an int value like the old collection). Basic usage:

import java.util.stream.Stream;

public class Demo09StreamCount {
	public static void main(String[] args) {
    	Stream<String> original = Stream.of("张无忌","周芷若");
    	Stream<String> result = original.filter(s -> s.startsWith("张"));
    	System.out.println(result.count()); // 2
	}
}

Use the first few: limit

The limit method can intercept convection, and only the first n can be used. Method signature:

Stream<T> limit(long maxSize);

The parameter is a long type. If the current length of the collection is greater than the parameter, it will be intercepted; Otherwise, do not operate. Basic usage:

import java.util.stream.Stream;

public class Demo10StreamLimit {
	public static void main(String[] args) {
    	Stream<String> original = Stream.of("张无忌","周芷若");
    	Stream<String> result = original.limit(2);
    	System.out.println(result.count()); // 2
	}
}

Skip the first few: skip

If you want to skip the first few elements, you can use the skip method to get a new stream after interception:

Stream<T> skip(long n);

If the current length of the stream is greater than N, skip the first n; Otherwise, you will get an empty stream with a length of 0. Basic usage:

import java.util.stream.Stream;

public class Demo11StreamSkip {
	public static void main(String[] args) {
    	Stream<String> original = Stream.of("张无忌","周芷若");
    	Stream<String> result = original.skip(2);
    	System.out.println(result.count()); // 1
	}
}

Combination: concat

If there are two streams and you want to merge them into one stream, you can use the static method concat of the stream interface:

static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b)

The basic usage code of this method is as follows:

import java.util.stream.Stream;

public class Demo12StreamConcat {
	public static void main(String[] args) {
    	Stream<String> streamA = Stream.of("张无忌");
    	Stream<String> streamB = Stream.of("张翠山");
    	Stream<String> result = Stream.concat(streamA,streamB);
	}
}

4.5 exercise: set element processing (traditional way)

subject

Now there are two ArrayList collections to store the names of multiple members in the team. The traditional for loop (or enhanced for loop) is required to perform the following steps in turn:

The codes of the two teams (sets) are as follows:

import java.util.ArrayList;
import java.util.List;

public class DemoArrayListNames {
	public static void main(String[] args) {
   	//第一支队伍
    	ArrayList<String> one = new ArrayList<>();

    	one.add("迪丽热巴");
    	one.add("宋远桥");
    	one.add("苏星河");
    	one.add("石破天");
    	one.add("石中玉");
    	one.add("老子");
    	one.add("庄子");
    	one.add("洪七公");

    	//第二支队伍
    	ArrayList<String> two = new ArrayList<>();
    	two.add("古力娜扎");
    	two.add("张无忌");
    	two.add("赵丽颖");
    	two.add("张三丰");
    	two.add("尼古拉斯赵四");
    	two.add("张天爱");
    	two.add("张二狗");
		// ....
	}
}

The code of the person class is:

public class Person {

	private String name;

	public Person() {}

	public Person(String name) {
    	this.name = name;
	}

	@Override
	public String toString() {
    	return "Person{name='" + name + "'}";
	}

	public String getName() {
    	return name;
	}

	public void setName(String name) {
    	this.name = name;
	}
}

answer

Since the traditional for loop writing method is used, then:

public class DemoArrayListNames {
	public static void main(String[] args) {
    	List<String> one = new ArrayList<>();
    	// ...

    	List<String> two = new ArrayList<>();
    	// ...

    	// 第一个队伍只要名字为3个字的成员姓名;
    	List<String> oneA = new ArrayList<>();
    	for (String name : one) {
        	if (name.length() == 3) {
            	oneA.add(name);
        	}
    	}

    	// 第一个队伍筛选之后只要前3个人;
    	List<String> oneB = new ArrayList<>();
    	for (int i = 0; i < 3; i++) {
        	oneB.add(oneA.get(i));
    	}

    	// 第二个队伍只要姓张的成员姓名;
    	List<String> twoA = new ArrayList<>();
    	for (String name : two) {
        	if (name.startsWith("张")) {
            	twoA.add(name);
        	}
    	}

    	// 第二个队伍筛选之后不要前2个人;
    	List<String> twoB = new ArrayList<>();
    	for (int i = 2; i < twoA.size(); i++) {
        	twoB.add(twoA.get(i));
    	}

    	// 将两个队伍合并为一个队伍;
    	List<String> totalNames = new ArrayList<>();
    	totalNames.addAll(oneB);
    	totalNames.addAll(twoB);

    	// 根据姓名创建Person对象;
    	List<Person> totalPersonList = new ArrayList<>();
    	for (String name : totalNames) {
        	totalPersonList.add(new Person(name));
    	}

    	// 打印整个队伍的Person对象信息。
    	for (Person person : totalPersonList) {
        	System.out.println(person);
    	}
	}
}

The operation result is:

Person{name='宋远桥'}
Person{name='苏星河'}
Person{name='石破天'}
Person{name='张天爱'}
Person{name='张二狗'}

4.6 exercise: set element processing (stream mode)

subject

Replace the traditional for loop writing method in the previous question with stream streaming processing method. The initial contents of the two collections remain unchanged, and the definition of the person class remains unchanged.

answer

The equivalent stream stream processing code is:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class DemoStreamNames {
	public static void main(String[] args) {
    	List<String> one = new ArrayList<>();
    	// ...

    	List<String> two = new ArrayList<>();
    	// ...

    	// 第一个队伍只要名字为3个字的成员姓名;
    	// 第一个队伍筛选之后只要前3个人;
    	Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

    	// 第二个队伍只要姓张的成员姓名;
    	// 第二个队伍筛选之后不要前2个人;
    	Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);

    	// 将两个队伍合并为一个队伍;
    	// 根据姓名创建Person对象;
    	// 打印整个队伍的Person对象信息。
    	Stream.concat(streamOne,streamTwo).map(Person::new).forEach(System.out::println);
	}
}

The operation effect is exactly the same:

Person{name='宋远桥'}
Person{name='苏星河'}
Person{name='石破天'}
Person{name='张天爱'}
Person{name='张二狗'}

Chapter V method reference

When using lambda expressions, the code we actually pass in is a solution: what parameters to take and what operations to do. Then consider a case: if the same scheme already exists in the operation scheme specified in lambda, is it necessary to write duplicate logic?

5.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));
    }
}

The printstring method only calls the print method of the printable interface, regardless of where the specific implementation logic of the print method will print the string. The main method specifies the functional interface printable through lambda expression. The specific operation scheme is: after getting the string (type can be deduced, so it can be omitted), output it in the console.

5.2 problem analysis

The problem with this code is that there is a ready-made implementation of the operation scheme of console printout of strings, that is, system The println (string) method in the out object. Since all lambda wants to do is call the println (string) method, why call it manually?

5.3 reference improvement code by method

Can you omit the syntax format of lambda (although it is already quite concise)? Just "reference" the past:

public class Demo02PrintRef {
    private static void printString(Printable data) {
        data.print("Hello,World!");
    }

    public static void main(String[] args) {
      	printString(System.out::println);
    }
}

The double colon:: writing method is called "method reference", and the double colon is a new syntax.

5.4 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.

semantic analysis

For example, in the above example, system There is an overloaded println (string) method in the 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:

The first semantics refers to: after getting the parameters, it is passed to the system through lambda out. Println method.

The semantics of the second equivalent writing method refers to: directly let system Out to 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.

The following code will call different overloaded forms of println method and change the functional interface to an int type parameter:

@FunctionalInterface
public interface PrintableInteger {
  	void print(int str);
}

Since the unique matching overload can be automatically derived after the context changes, the method reference does not change:

public class Demo03PrintOverload {
    private static void printInteger(PrintableInteger data) {
      	data.print(1024);
    }

    public static void main(String[] args) {
      	printInteger(System.out::println);
    }
}

This method reference will automatically match the overloaded form of println (int).

5.5 reference member method by object name

This is the most common usage, the same as the above example. If a member method already exists in a class:

public class MethodRefObject {
    public void printUpperCase(String str) {
      	System.out.println(str.toUpperCase());
    }
}

Functional interfaces are still defined as:

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

Then, when you need to use this printuppercase member method to replace the lambda of the printable interface, if you already have an object instance of the methodrefobject class, you can reference the member method through the object name. The code is:

public class Demo04MethodRef {
    private static void printString(Printable lambda) {
      	lambda.print("Hello");
    }

    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
}

5.6 reference static methods by class name

Because in Java The static method ABS already exists in lang. math class, so when we need to call this method through lambda, there are two ways to write it. The first is the functional interface:

@FunctionalInterface
public interface Calcable {
  	int calc(int num);
}

The first way is to use a lambda expression:

public class Demo05Lambda {
    private static void method(int num,Calcable lambda) {
      	System.out.println(lambda.calc(num));
    }

    public static void main(String[] args) {
      	method(-10,n -> Math.abs(n));
    }
}

But a better way to use method references is:

public class Demo06MethodRef {
    private static void method(int num,Math::abs);
    }
}

In this example, the following two expressions are equivalent:

5.7 member method by super reference

If there is an inheritance relationship, method references can also be used to replace super calls in lambda. The first is the functional interface:

@FunctionalInterface
public interface Greetable {
  	void greet();
}

Then the contents of the parent class human:

public class Human {
    public void sayHello() {
      	System.out.println("Hello!");
    }
}

Finally, the content of the subclass man, in which lambda's writing method is used:

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }

    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        //调用method方法,使用Lambda表达式
        method(()->{
            //创建Human对象,调用sayHello方法
            new Human().sayHello();
        });
        //简化Lambda
        method(()->new Human().sayHello());
        //使用super关键字代替父类对象
        method(()->super.sayHello());
    }
}

However, it is better to use method references to call the sayhello method in the parent class, such as another subclass, woman:

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        method(super::sayHello);
    }
}

In this example, the following two expressions are equivalent:

5.8 reference member methods through this

This represents the current object. If the method to be referenced is the member method in the current class, you can use the format of "this:: member method" to use the method reference. The first is a simple functional interface:

@FunctionalInterface
public interface Richable {
   	void buy();
}

The following is a husband husband class:

public class Husband {
    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(() -> System.out.println("买套房子"));
    }
}

The happy method behappy calls the marriage method Mary, whose parameter is the functional interface richable, so a lambda expression is required. However, if the contents of this lambda expression already exist in this class, you can modify the husband husband class:

public class Husband {
    private void buyHouse() {
      	System.out.println("买套房子");
    }

    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(() -> this.buyHouse());
    }
}

If you want to cancel the lambda expression and replace it with a method reference, the better way to write it is:

public class Husband {
    private void buyHouse() {
      	System.out.println("买套房子");
    }

    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(this::buyHouse);
    }
}

In this example, the following two expressions are equivalent:

5.9 constructor reference of class

Because the constructor name is exactly the same as the class name, it is not fixed. Therefore, the constructor reference is expressed in the format of class name:: new. The first is a simple person class:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Then there is the functional interface used to create the person object:

public interface PersonBuilder {
    Person buildPerson(String name);
}

To use this functional interface, you can use a lambda expression:

public class Demo09Lambda {
    public static void printName(String name,PersonBuilder builder) {
      	System.out.println(builder.buildPerson(name).getName());
    }

    public static void main(String[] args) {
      	printName("赵丽颖",name -> new Person(name));
    }
}

However, through constructor reference, there is a better way to write:

public class Demo10ConstructorRef {
    public static void printName(String name,Person::new);
    }
}

In this example, the following two expressions are equivalent:

5.10 constructor reference of array

Array is also a subclass of object, so it also has a constructor, but the syntax is slightly different. If you need a functional interface when corresponding to lambda's usage scenario:

@FunctionalInterface
public interface ArrayBuilder {
  	int[] buildArray(int length);
}

When applying this interface, you can use lambda expression:

public class Demo11ArrayInitRef {   
    private static int[] initArray(int length,ArrayBuilder builder) {
      	return builder.buildArray(length);
    }

    public static void main(String[] args) {
      	int[] array = initArray(10,length -> new int[length]);
    }
}

But a better way to write it is to use the constructor reference of the array:

public class Demo12ArrayInitRef {
    private static int[] initArray(int length,int[]::new);
    }
}

In this example, the following two expressions are equivalent:

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>