一、函数式接口
1. 概念
函数式接口:有且仅有一个抽象方法的接口,可以包含默认方法、静态方法、私有方法。
作用:专门用来支持 Lambda 表达式,Lambda 就是用来实现这个唯一抽象方法。
2. @FunctionalInterface 注解
-
写在接口定义上方,编译期校验,检查接口是否符合函数式接口规范
-
如果接口中抽象方法数量≠1,直接编译报错
-
注解属于可选,不加注解,只要满足只有 1 个抽象方法,依然是函数式接口
//标记为函数式接口
@FunctionalInterface
interface MyInterface{
//唯一抽象方法
void test();
//默认方法,不影响函数式接口
default void hello(){
System.out.println("默认方法");
}
//静态方法,不影响函数式接口
static void staticMethod(){
System.out.println("静态方法");
}
}
3. 函数式接口使用前提
Lambda 表达式的唯一使用场景就是函数式接口。
❗ 如果接口有 2 个及以上抽象方法,不能使用 Lambda,只能用匿名内部类。
二、Lambda 表达式
1. 本质
Lambda 是语法糖,底层依然生成对应实现类,简化函数式接口匿名内部类的冗余代码。
语法糖:编译器层面简化写法,没有新增底层语法。
2. 基础语法
(参数列表) -> {方法体};
():抽象方法的参数列表,无参数就空括号;->:Lambda 操作符,读作箭头,分隔参数和方法体;{}:方法体,写方法执行逻辑。
完整示例
@FunctionalInterface
interface Animal{
void eat(String food);
}
public class Test{
public static void main(String[] args) {
//完整Lambda写法
Animal cat = (String food) -> {
System.out.println("小猫吃:"+food);
};
cat.eat("小鱼干");
}
}
3. Lambda 省略规则(重点,初学者必记)
可以按照下面规则简化代码,能省则省:
-
参数类型:所有参数的类型可以一次性全部省略,不能只省略部分参数类型
-
单个参数:只有一个参数时,小括号 () 可以省略
-
方法体只有一行:大括号
{}可以省略,同时去掉这一行末尾的分号 -
一行 return:方法体只有 return 语句,省略
{}的同时,必须删掉 return 关键字@FunctionalInterface
interface Calc{
int add(int a, int b);
}
public class Test{
public static void main(String[] args) {
//完整版
Calc c1 = (int a, int b) -> {return a + b;};
//省略参数类型
Calc c2 = (a, b) -> {return a + b;};
//方法体仅一行return,省略{}、return
Calc c3 = (a, b) -> a + b;
System.out.println(c3.add(10,20));
}
}
单参数简化示例:
@FunctionalInterface
interface Say{
void sayHi(String name);
}
public class Test{
public static void main(String[] args) {
//完整写法
Say s1 = (String name) -> {System.out.println("Hi,"+name);};
//简化:单个参数,去掉括号
Say s2 = name -> System.out.println("Hi,"+name);
s2.sayHi("张三");
}
}
三、Lambda 访问外部局部变量(有效 final)
Lambda 访问方法内的局部变量,变量要求是有效 final
JDK8 新特性:变量只要赋值一次,后续不再修改,就算不写 final 关键字,也属于有效 final,可以访问。 如果代码中对变量二次赋值,直接编译报错。
public class Test{
public static void main(String[] args) {
int num = 10; //有效final,只赋值一次
Runnable run = () -> {
System.out.println(num);
};
run.run();
// num = 20; //打开这一行,编译报错!不能二次修改
}
}
原理:局部变量在栈内存,方法执行完毕销毁;Lambda 对象在堆内存,生命周期更长。为了保证数据一致性,不允许修改。
Lambda 中的 this
Lambda 里的this,代表外部类对象,不是 Lambda 本身。
和匿名内部类区分:匿名内部类的 this 指向匿名内部类自己。
public class Outer{
private String name = "外部类";
public void test(){
Runnable r = () -> {
//this指向Outer外部类对象
System.out.println(this.name);
};
r.run();
}
public static void main(String[] args) {
new Outer().test();
}
}
四、JDK 内置四大核心函数式接口(开发高频)
在java.util.function包,开发中直接使用,不需要自己定义接口
| 接口名 | 抽象方法 | 作用 |
|---|---|---|
Consumer<T> |
void accept(T t) |
消费型:有参数,无返回值,使用传入的数据 |
Supplier<T> |
T get() |
供给型:无参数,有返回值,对外提供数据 |
Function<T,R> |
R apply(T t) |
转换型:有参数有返回值,把 T 类型转为 R 类型 |
Predicate<T> |
boolean test(T t) |
判断型:有参数,返回布尔值,做条件判断 |
代码演示
-
Consumer 消费型
import java.util.function.Consumer;
public class Test{
public static void main(String[] args) {
//接收字符串,打印输出
Consumercon = str -> System.out.println(str);
con.accept("测试Consumer");
}
} -
Predicate 判断型
import java.util.function.Predicate;
public class Test{
public static void main(String[] args) {
//判断数字是否大于100
Predicatepre = num -> num>100;
System.out.println(pre.test(120)); //true
System.out.println(pre.test(50)); //false
}
}
五、方法引用(Lambda 进阶拓展)
场景:Lambda 里面仅仅是调用一个已经存在的方法,就可以继续简化为方法引用,进一步精简代码。 四种写法:
-
对象::成员方法
-
类名::静态方法
-
类名::成员方法
-
类名::new(构造器引用)
import java.util.function.Consumer;
public class Test{
public static void print(String s){
System.out.println(s);
}
public static void main(String[] args) {
//Lambda写法
Consumerc1 = s -> print(s);
//方法引用简化
Consumerc2 = Test::print;
c2.accept("方法引用演示");
}
}
六、Lambda VS 匿名内部类对比
| 对比项 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
| 适用范围 | 普通类、抽象类、接口,接口可以有多个抽象方法 | 仅函数式接口(只能 1 个抽象方法) |
| this 指向 | 匿名内部类自身对象 | 外部类对象 |
| 是否有 class 文件 | 生成独立 class 文件 | 不生成独立 class,语法糖 |
| 能不能写构造器 | 可以 | 不可以 |
七、高频易错点
@FunctionalInterface只是校验注解,不加注解,只要只有一个抽象方法,依旧是函数式接口;- Lambda不能用于多个抽象方法的接口,这种场景只能用匿名内部类;
- Lambda 中访问局部变量,变量不能二次赋值,遵守有效 final;
- Lambda 不是对象,只是函数式接口的实现简写;
- 省略规则不能混用:省略参数类型就要全部省略,不能只省一部分。
八、综合完整案例
@FunctionalInterface
interface Operate{
int calculate(int a,int b);
}
public class Demo{
public static void main(String[] args) {
//匿名内部类写法
Operate op1 = new Operate(){
@Override
public int calculate(int a, int b) {
return a-b;
}
};
System.out.println(op1.calculate(20,5));
//Lambda完整写法
Operate op2 = (a,b) -> {return a*b;};
System.out.println(op2.calculate(20,5));
//Lambda最简写法
Operate op3 = (a,b) -> a+b;
System.out.println(op3.calculate(20,5));
}
}