Lambda 表达式和函数式接口

一、函数式接口

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 省略规则(重点,初学者必记)

可以按照下面规则简化代码,能省则省:

  1. 参数类型:所有参数的类型可以一次性全部省略,不能只省略部分参数类型

  2. 单个参数:只有一个参数时,小括号 () 可以省略

  3. 方法体只有一行:大括号{}可以省略,同时去掉这一行末尾的分号

  4. 一行 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) 判断型:有参数,返回布尔值,做条件判断

代码演示

  1. Consumer 消费型

    import java.util.function.Consumer;
    public class Test{
    public static void main(String[] args) {
    //接收字符串,打印输出
    Consumer con = str -> System.out.println(str);
    con.accept("测试Consumer");
    }
    }

  2. Predicate 判断型

    import java.util.function.Predicate;
    public class Test{
    public static void main(String[] args) {
    //判断数字是否大于100
    Predicate pre = num -> num>100;
    System.out.println(pre.test(120)); //true
    System.out.println(pre.test(50)); //false
    }
    }

五、方法引用(Lambda 进阶拓展)

场景:Lambda 里面仅仅是调用一个已经存在的方法,就可以继续简化为方法引用,进一步精简代码。 四种写法:

  1. 对象::成员方法

  2. 类名::静态方法

  3. 类名::成员方法

  4. 类名::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写法
    Consumer c1 = s -> print(s);
    //方法引用简化
    Consumer c2 = Test::print;
    c2.accept("方法引用演示");
    }
    }

六、Lambda VS 匿名内部类对比

对比项 匿名内部类 Lambda 表达式
适用范围 普通类、抽象类、接口,接口可以有多个抽象方法 仅函数式接口(只能 1 个抽象方法)
this 指向 匿名内部类自身对象 外部类对象
是否有 class 文件 生成独立 class 文件 不生成独立 class,语法糖
能不能写构造器 可以 不可以

七、高频易错点

  1. @FunctionalInterface只是校验注解,不加注解,只要只有一个抽象方法,依旧是函数式接口;
  2. Lambda不能用于多个抽象方法的接口,这种场景只能用匿名内部类;
  3. Lambda 中访问局部变量,变量不能二次赋值,遵守有效 final;
  4. Lambda 不是对象,只是函数式接口的实现简写;
  5. 省略规则不能混用:省略参数类型就要全部省略,不能只省一部分。

八、综合完整案例

复制代码
@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));
    }
}
相关推荐
笔墨登场说说2 小时前
flink bin/start-cluster.sh 帮我做成开机启动
开发语言·python
计算机毕业设计杰瑞2 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计
小白学大数据2 小时前
拼多多反爬对抗实战:Scrapy 中间件化采集架构解析
开发语言·scrapy·中间件·架构
colinu3 小时前
C++ 异步任务与线程生命周期:`std::async`、`future`、`thread`、`jthread` 与协作式退出
java·jvm·c++
学逆向的3 小时前
win32注入代码
开发语言·网络安全·api·win32
蜗牛互联网3 小时前
Python + JSON Schema 实现工单结构化输出与本地复核
java·人工智能·后端
逐光者9333 小时前
STM32——SPI 屏 · Flash · 高速
java·前端·stm32
wuminyu3 小时前
ForkJoinPool窃取线程在扫描其他WorkQueue时的随机采样算法与Phase线程挂起唤醒机制解析
java·linux·c语言·jvm·c++
Sarvartha3 小时前
内部类知识
java·开发语言