--- title: "07-Lambda与函数式接口" created: 2026-09-02 tags: - Java --- # Lambda与函数式接口 > [!note] 本篇定位 > JDK 8 最重要的语言级变更:把"代码作为数据"带进 Java。Lambda 是写法,函数式接口是它赖以落地的类型系统,方法引用是 Lambda 的简写。学会它,Stream API([[08-Stream与Optional|08-Stream与Optional]])、回调、策略模式全部换一种写法。 ## 一、问题提出:行为参数化的老写法有多啰嗦 给"排序时按什么规则比"传一段逻辑,JDK 8 之前只能匿名内部类: ```java List names = new ArrayList<>(List.of("Charlie", "alice", "Bob")); names.sort(new Comparator() { @Override public int compare(String a, String b) { return a.compareToIgnoreCase(b); } }); ``` 五行里真正有信息量的只有一行。匿名内部类的痛点:**语法噪声淹没业务语义**。Lambda 把它压成一行: ```java names.sort((a, b) -> a.compareToIgnoreCase(b)); ``` ## 二、Lambda 语法:参数 → 箭头 → 体 ```java (参数列表) -> { 语句; } // 标准形态 () -> expression // 无参数、体是表达式(表达式的值就是返回值) x -> x * 2 // 单参数可省括号 (a, b) -> a + b // 体是表达式时省略 return 和分号 ``` 三条语法要点: 1. **参数类型可省**——编译器从目标类型推断(见下节"函数式接口"); 2. **体是单个表达式**时可省大括号与 return;多条语句必须 `{}` 且显式 return; 3. Lambda 是**表达式不是对象**——它没有独立的类型,类型由"赋值目标/参数位置"决定(这一点和匿名内部类有本质区别)。 **变量捕获规则**(面试高频,内部类同源问题):Lambda 可以引用外部的局部变量,但该变量必须是 **final 或事实最终(effectively final)**—— ```java int base = 10; // 没写 final,但之后没改过 → effectively final Function f = x -> x + base; // ✅ base = 20; // ❌ 一旦改了,上面的 f 编译报错 ``` 原因:Lambda 捕获的是变量的**值副本**(存在合成字段里),允许改会造成"两份变量各改各的"语义混乱。想跨 Lambda 累加就用数组、`AtomicInteger` 或改用 Stream 归约。 ## 三、函数式接口:Lambda 的类型载体 **只有一个抽象方法的接口**就是函数式接口(`@FunctionalInterface` 注解只做编译期校验,不加也行,加了编译器帮你把关): ```java @FunctionalInterface public interface Calculator { int calc(int a, int b); // 唯一抽象方法 } Calculator add = (a, b) -> a + b; // Lambda 赋给它,类型才成立 Calculator mul = (a, b) -> a * b; int r = add.calc(3, 4); // 7 ``` 接口里有没有别的方法不影响"函数式"判定——**default 方法和 static 方法不算抽象方法**(这正是 JDK 8 给接口加 default 的深层动机:让 Comparator、Runnable 这些老接口能携带公共逻辑而不破坏"函数式"资格,也更方便 JDK 自己演化)。 **JDK 内置四大函数式接口**——绝大多数场景不用自己定义: | 接口 | 抽象方法 | 语义 | 典型场景 | | --- | --- | --- | --- | | `Function` | `R apply(T t)` | 进 T 出 R | 映射转换 | | `Predicate` | `boolean test(T t)` | 断言真假 | 过滤条件 | | `Consumer` | `void accept(T t)` | 消费不返回 | foreach 副作用 | | `Supplier` | `T get()` | 无中生有 | 工厂/懒加载 | 加上带原始类型的变体(`IntPredicate` 避免装箱)、双参变体(`BiFunction`)、二元运算(`BinaryOperator`:两个 T 进一个 T 出)——`java.util.function` 包 40+ 接口都是这四大件的排列组合。 ## 四、方法引用:Lambda 的"高阶简写" 当 Lambda 体只是"调用一个已存在的方法"时,可以进一步简写为方法引用: ```java names.forEach(s -> System.out.println(s)); // Lambda names.forEach(System.out::println); // 方法引用:类名::实例方法 Function len = String::length; // s -> s.length() Supplier> maker = ArrayList::new; // 构造器引用 Function arrMaker = int[]::new; // 数组构造引用 BiFunction starts = String::startsWith; // (a,b) -> a.startsWith(b) ``` 四种形态:`类名::静态方法`、`对象::实例方法`、`类名::实例方法`(首参当接收者)、`类名::new`。判断能不能简写的口诀:**Lambda 体只干"把这个参数交给某个现成方法"这一件事,就能写 `::`**。 ## 五、Lambda vs 匿名内部类:同与不同(面试高频) | 维度 | 匿名内部类 | Lambda | | --- | --- | --- | | 语法 | 完整类结构,样板多 | 极简 | | this 含义 | 指向**匿名类实例** | 指向**外围实例**(没有自己的 this) | | 类型 | 编译期生成 `外部类$1.class` | 编译期是普通方法 + `invokedynamic` 指令,运行期才由 JVM 生成实现类 | | 覆盖范围 | 抽象类、多抽象方法接口都行 | **只能**函数式接口 | `invokedynamic` 的收益:Lambda 的实现策略(内联、生成类、MethodHandle)推迟到运行期由 JVM 决定,**字节码体积小、未来可优化**——这是"Lambda 比匿名内部类轻"的底层依据。 ## 六、实战:回调与策略的全部换血 **回调**(线程创建就是现成例子): ```java new Thread(() -> System.out.println("run in " + Thread.currentThread().getName())).start(); ``` **策略模式瘦身**:枚举/Map 持有 Lambda 代替一堆策略类—— ```java Map> strategies = Map.of( "asc", Integer::compareTo, "desc", (a, b) -> b.compareTo(a) ); list.sort(strategies.get("desc")); ``` **自己写"接受行为"的 API**:把 `Supplier` 当懒加载句柄、`Function` 当转换管道、`Predicate` 当校验规则组合——这是现代框架源码(Spring 的 `Function` 注入、MyBatis-Plus 的 Lambda 查询)的通用语言。 ## 七、高频面试题速答 1. **Lambda 和匿名内部类区别?** 见第五节表格——this 指向、实现机制(invokedynamic)、只限函数式接口。 2. **为什么捕获的局部变量要 final?** 捕获的是值副本,可变会引发副本与原变量状态不一致;成员变量不受限(通过 this 引用,改的是对象字段)。 3. **@FunctionalInterface 必须加吗?** 不必须(判定标准是"单抽象方法"),加了的收益是编译器校验 + 文档自明。 4. **函数式接口里能有 default 方法吗?** 能,default/static 不算抽象方法。 5. **方法引用什么时候用不了?** 需要对参数做前置加工、多语句、或方法调用不"唯一对应参数"时——老老实实写 Lambda。 ## 勾连 - 内部类与变量捕获的同源规则:[[02-内部类|02-内部类]] - effective final 与 final 语义:[[01-final关键字|01-final关键字]] - 函数式接口的最大用户:[[08-Stream与Optional|08-Stream与Optional]] - 接口 default 方法的冲突裁决:[[02-抽象类与接口|02-抽象类与接口]] --- ⬅️ [[06-枚举|06-枚举]] 🏠 [[00-Java|00-Java]] ➡️ [[08-Stream与Optional|08-Stream与Optional]]