# Lambda 运行机制

作者:Ethan.Yang
博客:https://blog.ethanyang.cn (opens new window)


Java 8 引入的 Lambda 表达式,极大简化了函数式接口的实现,提升了代码可读性和性能。
本文将系统解析 Lambda 的底层运行机制、变量捕获、方法引用以及 SerializedLambda 反射用法,帮助理解其与匿名内部类的本质差异以及在实际开发中的应用场景。

# 一、匿名类 vs Lambda

举例

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
};

Runnable r2 = () -> System.out.println("Hello");
1
2
3
4
5
6
7
8

可以达到一致的效果, 你可能以为 lambda 只是匿名内部类的“简写形式”。这是错误的!它们在运行时的本质差异非常大:

本质:匿名内部类是编译期生成类,lambda 是运行期生成函数对象

对比项 匿名内部类 Lambda 表达式
编译期是否生成类 ✅ 生成独立 class 文件 ❌ 不生成,靠 invokedynamic 动态生成
this 绑定 指向匿名类实例 指向外部类实例
底层实现 new 创建对象 invokedynamic + LambdaMetafactory
可序列化性 可通过类名直接序列化 默认不可,需要实现 Serializable

# 二、底层机制:invokedynamic

示例

Function<String, String> f = s -> s.toUpperCase();
System.out.println(f.apply("hello"));
1
2

字节码分析(使用 javap -c -p

invokedynamic #0, apply()Ljava/util/function/Function; // BootstrapMethod #0
1

背后的过程:

  1. 编译期不会生成类,而是生成 invokedynamic 字节码指令。
  2. 运行期JVM 通过 LambdaMetafactory.metafactory() 生成函数式接口实现类,并绑定到 MethodHandle
  3. 性能更优:JVM 有更大优化空间,例如内联等。

# 三、变量捕获机制

规则:只允许捕获“final 或 effectively final”变量

  1. 值捕获(primitive / immutable)

    int base = 10;
    Function<Integer, Integer> add = x -> x + base;
    
    1
    2
    • base 的值会被拷贝进 lambda 运行时对象中。

    • 本质上是“快照式副本”。

  2. 引用捕获(引用类型变量)

    StringBuilder sb = new StringBuilder("A");
    Runnable r = () -> sb.append("B");
    
    1
    2
    • lambda 捕获的是 sb 的引用,修改对象内部状态是合法的

    • 但不能改变 sb 指向另一个对象。

例子:不同的 lambda 实例

Runnable r1 = () -> System.out.println("Hi");
Runnable r2 = () -> System.out.println("Hi");
System.out.println(r1 == r2); // true,复用

int base = 10;
Runnable r3 = () -> System.out.println(base);
Runnable r4 = () -> System.out.println(base);
System.out.println(r3 == r4); // false,有状态,创建两个实例
1
2
3
4
5
6
7
8

# 四、方法引用对比

示例

Function<String, Integer> f1 = s -> Integer.parseInt(s);
Function<String, Integer> f2 = Integer::parseInt;
1
2
  • 方法引用只是 lambda 的语法替代形式,但实际底层一样:invokedynamic + LambdaMetafactory
  • 编译器推导出方法签名并绑定。

# 五、SerializedLambda 的反射用法

Lambda 可通过反射获取其底层结构,用于 AOP、ORM 工具中。

例子:提取方法名

// 实现 Serializable, 将 Lambda 表达式转换为 SerializedLambda 对象,进而通过反射分析其结构。
@FunctionalInterface
interface SFunction<T, R> extends Function<T, R>, Serializable {}

public class LambdaUtil {
	// 通过反射获取内部方法
    public static <T, R> String getMethodName(SFunction<T, R> fn) throws Exception {
        Method m = fn.getClass().getDeclaredMethod("writeReplace");
        m.setAccessible(true);
        SerializedLambda sl = (SerializedLambda) m.invoke(fn);
        return sl.getImplMethodName();
    }
}

// 测试
public static void main(String[] args) throws Exception {
    SFunction<User, String> fn = User::getName;
    Method m = fn.getClass().getDeclaredMethod("writeReplace");
    m.setAccessible(true);
    SerializedLambda lambda = (SerializedLambda) m.invoke(fn);

    System.out.println("类:" + lambda.getImplClass());
    System.out.println("方法名:" + lambda.getImplMethodName());
    System.out.println("签名:" + lambda.getImplMethodSignature());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

应用场景:

  • ORM 映射:通过 User::getName 提取字段名为 name
  • 日志/埋点:获取方法名做动态记录
  • AOP 注解处理:识别切入点函数