JavaSE高级 -JDK1.8新特性-Lambda表达式-函数式接口

一、Lambda表达式:
标准格式由三个部分组成:
一些参数
一个箭头
一段代码

Lambda表达式的标准格式:
(参数类型 参数名称)->{ 代码语句 }

格式说明:
小括号的语法与传统方法参数列表一致:无参数则留空,多个参数就用逗号隔开
-> 新引入的语法格式,代表指向动作
大括号内的语法和传统方法体的要求一致
总结:Lambda表达式简化匿名内部类,首先要求是接口,其次是该接口只有一个抽象方法。

无参数:
无参数:不需要任何条件即可执行该方案
无返回值:该方案不产生任何结果
代码块(方法体):该方案的具体执行步骤
同样的语义体现在Lambda中更简单:
()-> System.out.println("多线程执行!");

前面的一对小括号即run方法的参数(无),代表不需要任何条件。
中间的箭头代表将前面的参数传递给后面的代码。
后面的输出语句是业务逻辑代码。

有参数和返回值:
例如:Arrays.sort()
传统写法:
实际上只有参数和方法体是重要的
Lambda写法:
// Lambda
Arrays.sort(array, (Person a, Person b) -> {
return a.getAge() - b.getAge();
});

省略格式:
省略规则:
在Lambda标准格式基础上,使用省略的写法规则是:
小括号内的参数类型可省略;
如果小括号内有且仅有一个参数,则小括号可以省略;
如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、语句分号,必须省略return关键字。

可推导即可省略:
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。
//Runnable接口简化:

  1. () -> System.out.println("多线程任务执行!")

//Comparator接口简化:
2. Arrays.sort(array, (a, b) -> a.getAge() - b.getAge());

//ActionListener接口简化:
3.button.addActionListener(e -> System.out.println("按钮被点击了!"));

Lambda的前提条件:
Lambda的语法非常简洁,完全没有面向对象的束缚。但是使用时有几个问题需要特别注意:
使用Lambda 必须具有接口,且要求接口中有且仅有一个抽象方法。
无论时JDK内置的Runnable 、comparator 接口还是自定义接口,只有当接口中的抽象方法 存在且唯一时,才可以使用Lambda。
使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
ps:有且仅有一个抽象方法的接口,称为:函数式接口。

二、函数式接口
1.概念:
函数式接口(Funtional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。注解:@FuntionalInterface 可以检测接口是否是一个函数式接口。(在合作开发中,避免同事修改此接口造成错误)。
2.函数的定义和使用
定义一个函数式接口:
//问候
@FunctionalInterface
public interface GreetingService {
void sayMessage();
}

定义此函数式接口的实现类:
public class GreetingServiceImpl implements GreetingService {
@Override
public void sayMessage() {
System.out.println("say hello!");
}
}

函数式接口一般作为方法的参数和返回值类型:
public class Demo {
//定义一个方法,参数使用函数式接口GreetingService
public static void show(GreetingService greetingService){
greetingService.sayMessage();
}

public static void main(String[] args) {
    //调用show方法,方法的参数是一个接口,所以可以传递接口的实现类
    show(new GreetingServiceImpl());

    //调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
    show(new GreetingService() {
        @Override
        public void sayMessage() {
            System.out.println("使用匿名内部类重写接口中的抽象方法");
        }
    });
}

}

Lambda表达式的写法:
public class Demo {
//定义一个方法,参数使用函数式接口GreetingService
public static void show(GreetingService greetingService){
greetingService.sayMessage();
}

public static void main(String[] args) {
    //调用show方法,使用lambda表达式
    show(()->System.out.println("使用lambda重写接口中的抽象方法"));
}

}

3.Lambda表达式的好处:
优势:1.简化代码,更为简洁 2.延迟加载
有些场景的代码执行后,结果不一定会被使用,从而造成性能的浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

例如:性能浪费的日志案例:
ps: 日志可以帮助我们快速定位问题,记录程序运行中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下打印输出:
public class Test {
public static void main(String[] args) {
String msgA = "hello";
String msgB = "world";
String msgC = "java";
showLog(1, msgA + msgB + msgC);
}

private static void showLog(int level, String msg) {
//当日志级别为1时,打印日志
if (level == 1) {
System.out.println(msg);
}
}
}

这段代码存在的问题是:无论级别是否满足要求,作为showLog方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能的浪费。

使用Lambda 必然先定义一个函数式接口:
@FunctionalInterface
interface MessageBuilder{
String buildMessage();
}

对showLog方法进行改造:
public class Test {
public static void main(String [] ags){
String msgA = "hello";
String msgB = "world";
String msgC = "java";

    //lambda实现显示日志
    showLogLambda(1,()->msgA+msgB+msgC);
    
    //证明lambda表达式的延迟执行
    showLogLambda(1,()->{
        System.out.println("Lambda执行!");
        return msgA+msgB+msgC;
    });
}

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

}

这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会拼接。

三、常用的函数式接口:
四大核心函数接口:
函数式接口 参数类型 返回类型 用途
Supplier接口,供给型接口 无 T 返回的类型为T的对象,T get()
Consumer 接口,消费型接口 T void 对类型为T的对象应用操作,void accept(T t)
Function<T,R> 函数型接口 T R 根据类型T的参数获取类型R的结果,R apply(T t)
Predicate< T>接口,断定型接口 T boolean 用于条件判断的场景,boolean test(T t)

Supplier< T>接口: 函数式接口
T get();
供给型
特点:使用其get(),可以返回一个与接口泛型一致的数据。

Consumer< T>接口:函数式接口
accept(T t)
消费型
default andThen() 默认方法
特点:提供一个与泛型一致数据类型的参数。

Function<T,R>接口:函数式接口
R apply(T t)
default andThen(Function <T f) 组合操作
根据泛型的类型,将前者的T类型转化为R类型。

Predicate接口:函数式接口
boolean test(T t); 返回true、false。
default Predicate and (Predicate<? super T> other ) 与
default Predicate negate() 非
default Predicate or (Predicate<? super T> other) 或
对某种类型的数据进行判断,从而得到一个boolean值结果。

四、方法的引用:
方法的引用的概述:
方法引用使得开发者可以直接引用现存的方法,Java类的构造方法或者实例对象。方法的引用和Lambda表达式配合使用,使得Java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

静态方法的引用:
引用格式:
类名::静态方法
简化步骤:
定义一个静态方法,把需要简化的代码放到一个静态方法中。
注意事项:
被引用的方法的参数列表,要和函数式接口中的抽象方法的参数列表一致。
如果函数式接口中的抽象方法有返回值,则被引用的方法必须也有相同返回值。
如果函数式接口中的抽象方法没有返回值,则被引用的方法可以有返回值,也可以没有返回值。
实例方法的引用:
引用格式:
对象::实例方法
简化步骤:
定义一个实例方法,把需要的代码放到实例方法中。
注意事项:
被引用的方法的参数列表,要和函数式接口中的抽象方法的参数列表一致。

特定类型的方法引用:
引用格式:
特定类型::方法
特点类型:
String,任何类型
注意事项:
如果函数式接口中的抽象方法的参数列表中,第一个参数作为了后面的方法的调用者,并且
其余参数作为后面方法的形参,那么就可以用特定类型的方法引用了。

构造器的引用:
引用格式:
类名::new
注意事项:
函数式接口的抽象方法的形参列表和构造器的形参列表一致,抽象方法返回值类型即为构造器所属的类的类型。
类似于 s->new Student(s) =>Student::new

原文地址:https://www.cnblogs.com/monkeycode/p/14070952.html