JAVA Basics-Chapter 21 Functional Interface

main content

Custom functional interface
Functional programming
Common functional interface

teaching objectives

Ability to use @FunctionalInterface annotation
Ability to customize functional interfaces with no parameters and no return
Ability to customize functional interfaces with parameters and return
Ability to understand the characteristics of Lambda delayed execution
Ability to use Lambda as method parameters
Ability to use Lambda as method return value
Ability to use Supplier Functional interface
Can use Consumer functional interface
Can use Function functional interface
Can use Predicate functional interface

Chapter 1 Functional Interface

1.1 Concept

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

The functional interface is an interface suitable for functional programming scenarios. The functional programming in Java is Lambda, so functional interface is an interface that can
be applied to Lambda. Lambda in Java can be deduced smoothly only by ensuring that there is one and only one abstract method in the interface.

备注:“语法糖”是指使用更加方便,但是原理不变的代码语法。例如在遍历集合时使用的for-each语法,其实
底层的实现原理仍然是迭代器,这便是“语法糖”。从应用层面来讲,Java中的Lambda可以被当做是匿名内部
类的“语法糖”,但是二者在原理上是不同的。

1.2 Format

Just make sure 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:

1.3 @FunctionalInterface annotation

Similar to the function of the @Override annotation, Java 8 introduced a new annotation specifically for functional interfaces: @FunctionalInterface. This annotation
can be used in the definition of an interface:

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

1.4 Custom functional interface

For the just-defined MyFunctionalInterface functional interface, the typical usage scenario is as a method parameter:

Chapter 2 Functional Programming

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

2.1 Delayed execution of Lambda

public interface MyFunctionalInterface {
void myMethod();
}
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) {
inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法
doSomething(() ‐> System.out.println("Lambda执行啦!"));
}
}

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

Log case of wasteful performance

Note: The log can help us quickly locate the problem and record the situation during the operation of the program for the monitoring and optimization of the project.

A typical scenario is to use parameters conditionally, for example, after splicing log messages, print out if the conditions are met:

There is a problem with this code: Regardless of whether the level meets the requirements, 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 is performed. If the level does not meet the requirements, the string splicing operation is done in vain, and there is a waste of performance.

备注:SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行
字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进
行字符串拼接。例如:LOGGER.debug("变量{}的取值为{}。", "os", "macOS"),其中的大括号{}为占位
符。如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字
符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。

Experience the better writing of Lambda

To use Lambda, a functional interface is necessary:

Then modify the log method:

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);
    }
}
@FunctionalInterface
public interface MessageBuilder {  
String buildMessage();
}
public class Demo02LoggerLambda {
private static void log(int level, MessageBuilder builder) {
if (level ==  1 ) {
System.out.println(builder.buildMessage());
        }
    }

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

Proof of Lambda delay

The following code can be verified by the results:

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

扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法
来完成。而是否调用其所在方法是在条件判断之后才执行的。

2.2 Use Lambda as a parameter and return value

If the implementation principle is aside, Lambda expressions in Java can be regarded as a substitute for anonymous inner classes. If the parameter of the method is a functional
interface type, then Lambda expressions can be used instead. Using Lambda expressions as method parameters is actually using functional
interfaces as method parameters.

For example, the java.lang.Runnable interface is a functional interface. If there is a startThread method that uses this interface as a parameter, then
Lambda can be used to pass parameters. In this case, there is no essential difference from the Runnable parameter of the Thread class.

public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log( 1 , () ‐> msgA + msgB + msgC );
    }
}
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;
        });
    }
}

Similarly, if the return value type of a method is a functional interface, then a Lambda expression can be returned directly. When
a method is needed to obtain an object of the java.util.Comparator interface type as a sorter, this method can be called to obtain it.

Which directly returns a Lambda expression.

Chapter 3 Commonly Used Functional Interface

JDK provides a large number of commonly used functional interfaces to enrich the typical usage scenarios of Lambda. They are mainly provided in the java.util.function package.
The following are the simplest interfaces and usage examples.

3.1 Supplier interface

The java.util.function.Supplier interface contains only one method without parameters: T get(). It is used to get the
object data of a type specified by a generic parameter . Since this is a functional interface, it means that the corresponding Lambda expression needs to "provide externally" an object
data that conforms to the generic type .

public class Demo04Runnable {
private static void startThread(Runnable task) {
new Thread(task).start();
    }
public static void main(String[] args) {
startThread(() ‐> System.out.println("线程任务执行!"));
    }
}
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));
    }
}

3.2 Exercise: Find the maximum value of an array element

topic

Use the Supplier interface as the method parameter type, and use the Lambda expression to find the maximum value in the int array. Reminder: Please use
java.lang.Integer class for generic interface .

answer

3.3 Consumer interface

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

The java.util.function.Consumer interface is just the opposite of the Supplier interface. Instead of producing a piece of data, it consumes a piece of data.
The data type is determined by the generic type.

Abstract method: accept

Consumer接口中包含抽象方法void accept(T t),意为消费一个指定泛型的数据。基本使用如:

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

Default method: andThen

If the parameters and return value of a method are all Consumer types, then the effect can be achieved: When consuming data, first do an operation,
and then do an operation to achieve combination. And this method is the default method andThen in the Consumer interface. The following is the source code of the JDK:

备注:java.util.Objects的requireNonNull静态方法将会在参数为null时主动抛出
NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

To achieve the combination, two or more Lambda expressions are needed, and the semantics of andThen is exactly the "step by step" operation. For example, the combination of two steps
:

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));
    }
}
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) ‐> { accept(t); after.accept(t); };
}
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 result of the operation will first print HELLO in completely uppercase, and then print hello in completely lowercase. Of course, more steps can be
combined through chain writing .

3.4 Exercise: Format printing information

topic

There are multiple pieces of information in the following string array, please print the information according to the format "name: XX. gender: XX.". Request to print last name

The action of name is taken as the Lambda instance of the first Consumer interface, and the action of printing gender is taken as the Lambda instance of the second Consumer interface
, and the two Consumer interfaces are "spliced" together in order.

answer

3.5 Predicate interface

Sometimes we need to judge a certain type of data to get a boolean value result. At this time, you can use the
java.util.function.Predicate interface.

Abstract method: test

Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景:
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
}
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); // 姓名:迪丽热巴。性别:女。
        }
    }
}

The criterion for conditional judgment is the logic of the incoming Lambda expression. As long as the string length is greater than 5, it is considered very long.

Default method: and

Since it is a conditional judgment, there will be three common logical relationships with, or, and not. When connecting two Predicate conditions with "and" logic to
achieve the effect of "and", you can use the default method and. The JDK source code is:

If you want to determine that a string contains both uppercase "H" and uppercase "W", then:

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:

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 );
    }
}
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) && other.test(t);
}
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 Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) ‐> test(t) || other.test(t);
}

If you want to implement the logic "string contains uppercase H or uppercase W", then the code only needs to modify the name "and" to "or", and everything else
remains the same:

Default method: negate

"And" and "or" are already understood, and the remaining "not" (negative) will be simple. The JDK source code of the default method negate is:

It is easy to see from the implementation that after the test method is executed, the result boolean value is reversed with "!". The
negate method must be called before the test method is called , just like the and and or methods:

3.6 Exercise: Collection information filtering

topic

There are multiple pieces of "name + gender" information in the array as follows. Please filter the strings that meet the requirements into the collection
ArrayList through the assembly of the Predicate interface. Two conditions must be met at the same time:

1. 必须为女生;
2. 姓名为 4 个字。
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 Predicate<T> negate() {
return (t) ‐> !test(t);
}
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 );
    }
}

answer

3.7 Function interface

The java.util.function.Function<T,R> interface is used to obtain data of one type according to the data of another type. The former is called a precondition and the
latter is called a postcondition.

Abstract method: apply

Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。

The scenario used is for example: converting the String type to the Integer type.

public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
    }
}
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;
    }
}

Of course, it is best to use method references.

Default method: andThen

Function接口中有一个默认的andThen方法,用来进行组合操作。JDK源代码如:

This method is also used in the "do what first, then do what" scenario, similar to andThen in Consumer:

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 by andThen in sequence
.

请注意,Function的前置条件泛型和后置条件泛型可以相同。

3.8 Exercise: Custom function model stitching

topic

Please use Function to join the function model. The multiple function operations that need to be executed in order are:

String str = "Zhao Liying, 20";

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

1. Intercept the numeric age part of the string to get the string;

2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字累加 100 ,得到结果int数字。

answer

import^ java.util.function.Function;
public class DemoFunction {
public static void main(String[] args) {
String str = "赵丽颖,20";
int age = getAgeNum(str, s ‐> s.split(",")[ 1 ],
s ‐>Integer.parseInt(s),
n ‐> n +=  100 );
System.out.println(age);
    }
private static int getAgeNum(String str, Function<String, String> one,
Function<String, Integer> two,
Function<Integer, Integer> three) {
return one.andThen(two).andThen(three).apply(str);
    }
}

Guess you like

Origin blog.csdn.net/weixin_43419256/article/details/108230785