哈哈,是不是很神奇!让我详细解释一下这个机制:

SerializedLambda 的魔法 ✨

1. Java Lambda 的序列化机制

当一个 lambda 或方法引用实现了 Serializable 接口时,Java 编译器会做特殊处理:

// 你写的代码
Fn<FooClass, String> fn = this::handler;

// 编译器生成的 Lambda 类会包含一个特殊方法
class Main$$Lambda$1 implements Fn, Serializable {
    private Object writeReplace() {
        return new SerializedLambda(
            Main.class,           // 捕获的类
            "handler",            // 实际方法名
            "(LFooClass;)LString;", // 方法签名
            // ... 其他元信息
        );
    }
}

2. SerializedLambda 是什么?

SerializedLambda 是 Java 标准库中的一个类(java.lang.invoke.SerializedLambda),它保存了 Lambda 的完整元信息

public final class SerializedLambda implements Serializable {
    private final Class<?> capturingClass;      // 捕获 lambda 的类
    private final String functionalInterfaceClass; // Fn 接口
    private final String implClass;              // 实现方法所在的类 (Main)
    private final String implMethodName;         // 实现方法名 (handler)
    private final String implMethodSignature;    // 方法签名
    private final int implMethodKind;            // 方法类型 (实例方法/静态方法)
    // ...
}

3. 为什么之前失败了?

之前调用 writeReplace() 失败是因为 Fn 接口没有继承 Serializable,所以:

4. 加上 Serializable 后发生了什么?

fun interface Fn<A, R> : Serializable {  // 关键:继承 Serializable
    fun invoke(a: A): R
}

现在编译器知道这个接口需要序列化支持,会:

  1. 为每个 lambda/方法引用生成 writeReplace() 方法

  2. writeReplace() 中创建 SerializedLambda 对象

  3. SerializedLambda 包含了原始方法的完整信息

5. 提取信息的过程

// 1. 调用 writeReplace 获取 SerializedLambda
val writeReplace = fn.javaClass.getDeclaredMethod("writeReplace")
val serializedLambda = writeReplace.invoke(fn)

// 2. SerializedLambda 有这些 getter 方法:
val implClass = serializedLambda.getImplClass()        
// 返回: "Main" (内部格式: "Main")

val implMethodName = serializedLambda.getImplMethodName()  
// 返回: "handler"

val implMethodSignature = serializedLambda.getImplMethodSignature() 
// 返回: "(LMain$FooClass;)Ljava/lang/String;"

// 3. 用这些信息反射获取真实方法
Class.forName("Main")
    .getDeclaredMethod("handler", FooClass.class)

6. 完整的信息流

Java 代码: this::handler
        ↓
编译器生成: Main$$Lambda$123 implements Fn, Serializable
        ↓
包含方法: writeReplace() → SerializedLambda
        ↓
SerializedLambda 保存:
  - implClass: "Main"
  - implMethodName: "handler"  
  - implMethodSignature: "(LMain$FooClass;)Ljava/lang/String;"
        ↓
我们的代码: 通过反射读取这些信息
        ↓
找到真实方法: Main.handler(FooClass)
        ↓
获取泛型类型: method.genericParameterTypes[0] = FooClass.class

7. 为什么这么神奇?

因为 Java 序列化机制要求保留足够的信息来重建对象。对于 Lambda:

所以 SerializedLambda 必须保存这些元信息,我们就可以"借用"这个机制来获取方法引用的真实信息!

8. 对比其他方案

9. 注意事项

⚠️ 唯一的代价Fn 接口继承了 Serializable

fun interface Fn<A, R> : Serializable {
    fun invoke(a: A): R
    
    // 可选:禁止真正的序列化
    @Throws(java.io.NotSerializableException::class)
    private fun writeObject(out: java.io.ObjectOutputStream) {
        throw java.io.NotSerializableException("Fn should not be serialized")
    }
}

这样既能利用 SerializedLambda 的元信息,又能防止意外序列化!

太优雅了对吧? 这就是为什么了解 JVM 内部机制这么重要!🎉