← 返回题目列表

Lambda 表达式和函数式接口是什么?有哪些使用限制?

高频 简单 第 1 / 24 题 更新于 2026/07/26
Lambda函数式接口方法引用effectively final

简化版

Lambda 是函数式接口实例的简洁写法,函数式接口只包含一个抽象方法。Lambda 的具体类型由目标上下文决定,捕获的局部变量必须是 final 或 effectively final,但可以读取和修改对象内部的状态。

详细版

@FunctionalInterface 用于声明设计意图并让编译器检查接口是否满足规则,但不是函数式接口成立的必要条件。defaultstatic 方法以及与 Object 公有方法签名一致的方法不计入唯一抽象方法,因此接口仍可提供多个非抽象方法。

常用内置接口包括:

  • Predicate<T>:接收参数并返回 boolean
  • Function<T, R>:把 T 转换为 R
  • Consumer<T>:消费参数,不返回结果。
  • Supplier<T>:不接收参数,提供一个结果。

Lambda 依赖目标类型,同一个 (x) -> x + 1 可以赋给不同的兼容函数式接口。它不是匿名内部类的语法糖:Lambda 中的 this 指向外围对象,也不会创建一套新的 this 语义。

完整版教学

一、函数式接口如何判定

函数式接口的核心是只有一个抽象方法,也称 SAM(Single Abstract Method)。例如:

@FunctionalInterface
interface Converter<T, R> {
    R convert(T value);

    default R convertTwice(T value) {
        return convert(value);
    }
}

default 方法已经有实现,不会破坏 SAM 约束;静态方法属于接口本身,也不参与计算。@FunctionalInterface 可省略,但建议保留,因为后来误加第二个抽象方法时编译器会直接报错。

二、Lambda 依赖目标类型

Lambda 本身没有脱离上下文的独立类型,赋值、传参、返回值或强制类型转换会提供目标函数式接口:

Predicate<String> empty = text -> text.isEmpty();
Function<String, Integer> length = String::length;
Consumer<String> printer = System.out::println;
Supplier<List<String>> factory = ArrayList::new;

方法引用是 Lambda 的另一种表达形式,前提是参数和返回值能适配目标接口。它不是调用方法,而是描述稍后如何调用方法。

三、为什么局部变量必须 effectively final

Lambda 可以捕获外围作用域的局部变量,但该变量在初始化后不能再赋值:

int threshold = 10;
Predicate<Integer> large = value -> value > threshold;
// threshold++; // 编译失败

局部变量随方法栈帧结束而消失,Lambda 捕获的是其值而不是共享一个可变局部变量槽。限制重新赋值可以避免生命周期和并发可见性产生歧义。

注意,引用不能重新赋值不等于对象不可变。下面代码可以编译,但共享可变集合仍可能带来线程安全问题:

List<String> names = new ArrayList<>();
Consumer<String> add = names::add;

四、异常与重载陷阱

Lambda 能否抛出受检异常,取决于函数式接口抽象方法的 throws 声明。Function.apply 没有声明受检异常,因此直接调用会抛受检异常的方法无法通过编译,需要在 Lambda 内处理或定义合适的函数式接口。

当重载方法分别接收形状相同的函数式接口时,Lambda 可能无法让编译器唯一确定目标类型。这时应减少含糊重载,或通过显式参数类型、变量声明、类型转换帮助编译器选择。

五、Lambda 与匿名内部类的区别

匿名内部类会创建一个新的类作用域,其 this 指向匿名对象;Lambda 不引入新的 this,其中的 this 与外围上下文相同。两者的类型推断、作用域和运行时实现都不应视为完全等价,只是在许多回调场景中可以表达相似行为。

六、用捕获与调用次数理解代价

无捕获 Lambda 与捕获 Lambda 的运行时实例化策略属于实现细节,不能承诺每次都创建新对象或一定复用单例。正确做法是按语义写代码,再用 JMH 和分析工具测量热点;不要依据匿名内部类的对象模型推断 Lambda 身份。

假设一个流处理 1,000,000 个元素,map 中的 Lambda 最多可能执行 1,000,000 次,但短路、优化和数据过滤会改变实际次数。Lambda 若每次做网络调用,API 写法再简洁也仍是 1,000,000 次潜在 I/O;函数式风格不会自动批处理或缓存。

形式this 含义是否有独立类作用域主要用途
Lambda外围实例SAM 行为参数
匿名内部类匿名对象需要独立对象语义/多方法
方法引用由目标签名适配调用已有方法的简写

记忆钩子:Lambda 先找目标 SAM,再检查参数、返回值和 throws 是否兼容;捕获局部变量是捕获值,不是共享可变栈槽。

七、常见误区与追问

  • 误区:标注 @FunctionalInterface 才能使用 Lambda。 注解不是成立条件,只是让编译器保护设计意图。
  • 误区:函数式接口只能声明一个方法。 限制的是一个抽象方法,default、static 和符合规则的 Object 方法不计入。
  • 误区:Lambda 就是匿名内部类的纯语法糖。 两者的 this、作用域、类型和运行时实现都不同。
  • 误区:effectively final 会让捕获对象变成不可变对象。 只限制局部变量引用重新赋值,对象内部仍可变并可能存在并发风险。
  • 追问:方法引用何时不能替代 Lambda? 当需要额外参数变换、多步逻辑或 throws 不兼容时,不能仅靠方法引用适配。
  • 追问:Lambda 为什么可能导致重载歧义? 形状相同的多个函数式接口都可能成为目标类型,需要显式变量、参数类型或强制转换消歧。
  • 追问:Lambda 能否抛受检异常? 取决于目标 SAM 的 throws;标准 Function 未声明时必须捕获、包装或改用自定义接口。

八、加强记忆

函数式接口看的是唯一抽象方法,不是方法总数;@FunctionalInterface 是编译期护栏。Lambda 必须依附目标类型,捕获的局部变量要保持 final 或 effectively final,方法引用则是与目标接口签名兼容的简写。