一、函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口,接口中可以包含其他的方法(默认,静态,私有)

函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。
只有确保接口中有且仅有一个抽象方法,java中的Lambda才能顺利地进行推导。

语法糖:

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

注解:

@Override注解
检查方法是否为重写的方法
    是:编译成功
    否:编译失败

@FunctionalInterface注解
作用:可以检查接口是否是一个函数式接口
    是:编译成功
    否:编译失败(接口中没有抽象方法或者接口中的抽象方法多于1个)

二、Lambda表达式

1.Lambda的特点:

延迟加载,即满足条件才会执行,不满足条件就不执行。

2.Lambda的使用前提:

必须存在函数式接口

3.Lambda表达式作为方法的参数

如果抛开实现原理不说,java中的Lambda表达式可以被当做是匿名内部类的替代品。

如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。

使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如java.lang.Runnable接口就是一个函数式接口,
假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
这种情况其实和Thread类的构造方法参数为Runnable没有本质区别。

使用Lambda表达式作为参数的前提:

方法的参数是函数式接口

4.Lambda表达式作为方法的返回值

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。

当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调用该方法获取,代码如下:

public static Comparator<String> getComparator(){
    //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
	//        return new Comparator<String>() {
	//            @Override
	//            public int compare(String o1, String o2) {
	//                //排序规则:按照字符串的降序排序
	//                return o2.length() - o1.length();
	//            }
	//        };

    //方法的返回值类型是一个函数式接口,所以可以返回一个Lambda表达式
	//        return (String o1, String o2)->{
	//            //排序规则:按照字符串的降序排序
	//            return o2.length() - o1.length();
	//        };

    //优化Lambda
    //参数的数据类型可以省略:String
    //方法体只有一条语句,return可以省略,{}和;可以省略
    return (o1, o2)->o2.length() - o1.length();
}

三、常用的函数式接口

1.Supplier接口

java.util.function.Supplier接口仅包含一个无参的方法:T.get()。

用来获取一个泛型参数指定类型的对象数据。

Supplier接口被称为【生产型接口】,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据。

2.Consumer接口

2.1 java.util.function.Consumer
该接口正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。

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

Consumer接口是一个消费型接口,泛型指定什么类型,就可以使用accept方法消费什么类型的数据。至于具体怎么消费(使用),需要自定义(例如输出,计算等)

2.2 Consumer接口的默认方法andThen

作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费

例如:

Consumer<String> con1
Consumer<String> con2
String s = "java";
con1.accept(s);
con2.accept(s);

使用andThen进行优化:连接两个Consumer接口,再对数据进行消费
//谁写在前面,谁就先进行消费
//con1先进行消费,然后是con2
con1.andThen(con2).accept(s);

3.Predicate接口

作用:对某种数据类型的数据进行判断,结果返回一个boolean值

Predicate接口中包含一个抽象方法:

boolean test<T t>:用来对指定数据类型数据进行判断的方法
    结果:
        符合条件:返回true
        不符合条件:返回false

逻辑表达式:可以连接多个判断的条件

&&:与运算符,有false则false
||:或运算符,有true则true
!:非运算符:true则false;false则true

3.1 and方法

Predicate接口中有一个and方法,表示并且关系,也可以用于连接两个判断条件

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

可见and方法内部的两个判断条件,也是使用&&运算符连接起来的

3.2 or方法

Predicate接口中有一个or方法,表示或者关系,也可以用于连接两个判断条件

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

可见or方法内部的两个判断条件,也是使用||运算符连接起来的

3.3 negate方法

Predicate接口中有一个方法negate,表示取反的意思

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

4.Function接口

1.Function接口及其抽象方法

java.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据,
前者称为前置条件,后者称为后置条件。即该接口用于【类型转换】。

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

2.Function接口中的默认方法andThen

作用:进行组合操作

需求:

把String类型的“123”转换为Integer,把转换后的结果加10

把增加之后的Integer类型的数据,转换为String类型

分析:

转换了两次:

第一次是把String类型转换为Integer类型
    可以使用Function<String Integer> fun1
        Integer i = fun1.apply("123") + 10;
        
第二次是把Integer类型转化为String类型
    可以使用Function<Integer, String> fun2
        String s = fun2.apply(i);
        
可以使用andThen方法,把两次转换组合在一起使用
    String s = fun1.andThen(fun2).apply("123");
    fun1先调用apply方法,把字符串转换为Integer
    fun2再调用apply方法,把Integer转换为字符串