lambda表达式原理

111 阅读2分钟

[TOC]

当前java版本为java17

lambda经典写法

先看几个lambda的定义

/// 匿名函数式lambda表达式
Runnable r = () -> {};
Function<Integer, Integer> func = x -> x + 1;

// 静态方法引用式lambda表达式
BiFunction<HashSet<String>, String, Boolean> func2 = HashSet::add;

Set<String> set = new HashSet<>();
// 实例方法引用式lambda表达式
Function<String, Boolean> func3 = set::add;

// 静态方法引用式lambda表达式; 这里调用的方法直接就是静态方法
Function<Object, Object> f = Objects::requireNonNull;

// lambda的优化
Function<Object, Object> func = Objects::requireNonNull;
Consumer<String> optimizeFunc = Objects::requireNonNull;


// 构造器引用式lambda表达式
// 无参构造
Supplier<List<Integer>> func4 = ArrayList::new;
// 有参构造ArrayList(size)
Function<Integer, List<Integer>> func5 = ArrayList::new;

// lambada表达式实现接口
Function<Student, String> func = (Function<Student, String> & Serializable) Student::getName;

lambda原理探究

说明: 跟着代码debug的时候不要使用@Test注解的方法去跟, 因为它也用了动态语言脚本技术, 直接用main方法测试即可

Runnable r = () -> {}

public class LambdaView {
	public static void main(String[] args) {
        // 函数式接口
        Runnable r = () -> {};
        System.out.println("lambda类的简单类名:" + r.getClass().getSimpleName());
        System.out.println("lambda类的名字:" + r.getClass().getName());
        System.out.println("lambda类是否为匿名类:" + r.getClass().isAnonymousClass());    // false
        // 也可以用 r.getClass().getEnclosingClass() != null 判断是否有外围类
        System.out.println("lambda类是否为内部类:" + r.getClass().isMemberClass());    // false
        System.out.println("lambda类是否为本地类(局部类):" + r.getClass().isLocalClass());    // false
        System.out.println("lambda类是否为隐藏类:" + r.getClass().isHidden());    // true
        System.out.println("lambda类是否为动态生成的类:" + r.getClass().isSynthetic());    // true

        Runnable r2 = new Runnable() {
            @Override
            public void run() {

            }
        };
        System.out.println("匿名内部类的简单类名:" + r2.getClass().getSimpleName());
        System.out.println("匿名内部类的名字:" + r2.getClass().getName());
        System.out.println(r2.getClass().isAnonymousClass());   // true

        MyRunnable r3 = new MyRunnable();
        System.out.println("常规内部类的简单类名:" + r3.getClass().getSimpleName());
        System.out.println(r3.getClass().getName());
        System.out.println(r3.getClass().isAnonymousClass());   // false
	}	
	static class MyRunnable implements Runnable {
        @Override
        public void run() {

        }
    }
}

输出结果

lambda类的简单类名:LambdaView$$Lambda$14/0x0000000801001208
lambda类的名字:per.qiao.myutils.lambda.LambdaView$$Lambda$14/0x0000000801001208
lambda类是否为匿名类:false
lambda类是否为内部类:false
lambda类是否为本地类(局部类):false
lambda类是否为隐藏类:true
lambda类是否为动态生成的类:true
匿名内部类的简单类名:
匿名内部类的名字:per.qiao.myutils.lambda.LambdaView$1
true
常规内部类的简单类名:MyRunnable
per.qiao.myutils.lambda.LambdaView$MyRunnable
false

我们看到lambda表达式的Runnable r的类名包含了$$Lambda$, 它既不是内部类也不是本地类也不是匿名类, 而是一个隐藏类,一个动态生成的类

接下来我们单独研究这个r

public class LambdaView {

    public static void main(String[] args) {
        // 函数式接口
        Runnable r = () -> {};
    }
}

使用javap -c查看class文件的字节码

javap -c LambdaView.class

结果如下

public class per.qiao.myutils.lambda.LambdaView {
  public per.qiao.myutils.lambda.LambdaView();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
       5: astore_1
       6: return
}

第一个code部分是LambdaView类的无参构造部分, 我们重点关注第二个code部分

其中有个invokedynamic指令, 它是java7中引入的一种动态方法调用指令, 主要是为了支持动态类型语言和脚本语言在JVM上的高效实现。

与传统的 invokevirtual 或 invokestatic 不同,invokedynamic 不直接绑定到特定方法,而是使用引导方法 (bootstrap method) 动态决定调用目标。

Java 中的 lambda 表达式,引导方法通常是 java.lang.invoke.LambdaMetafactory.metafactory 方法。

这里的代码Runnable r = () -> {}会调用到LambdaMetafactory#metafactory方法

调用链路: JVM => MethodHandleNatives#linkCallSite =>CallSite#makeSite=>BootstrapMethodInvoker.invoke

LambdaMetafactory#metafactory

/**
     * 此方法用于动态生成 lambda 表达式实现,并返回一个 CallSite,用于实际调用 lambda 实现的目标。
     * 
     * @param caller    用于查找类和方法的上下文
     * @param interfaceMethodName   要实现的接口方法名
     * @param factoryType   返回lambda工厂的类型
     * @param interfaceMethodType   接口方法的签名
     * @param implementation    目标实现的句柄
     * @param dynamicMethodType 实际调用的动态方法签名
     * @return
     * @throws LambdaConversionException
     */
public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String interfaceMethodName,
                                       MethodType factoryType,
                                       MethodType interfaceMethodType,
                                       MethodHandle implementation,
                                       MethodType dynamicMethodType)
            throws LambdaConversionException {
    AbstractValidatingLambdaMetafactory mf;
    mf = new InnerClassLambdaMetafactory(Objects.requireNonNull(caller),
                                         Objects.requireNonNull(factoryType),
                                         Objects.requireNonNull(interfaceMethodName),
                                         Objects.requireNonNull(interfaceMethodType),
                                         Objects.requireNonNull(implementation),
                                         Objects.requireNonNull(dynamicMethodType),
                                         false,
                                         EMPTY_CLASS_ARRAY,
                                         EMPTY_MT_ARRAY);
    mf.validateMetafactoryArgs();
    return mf.buildCallSite();
}

debug看一下各个参数的值

caller = per.qiao.myutils.lambda.LambdaView
interfaceMethodName=run
factoryType=()Runnable
interfaceMethodType=()void
implementation=MethodHandle()void
dynamicMethodType=()void

这里我们关注一下buildCallSite方法

@Override
CallSite buildCallSite() throws LambdaConversionException {
    final Class<?> innerClass = spinInnerClass();
	// .... 省略代码
	try {
        Object inst = ctrs[0].newInstance();
        return new ConstantCallSite(MethodHandles.constant(interfaceClass, inst));
    }
}

private Class<?> spinInnerClass() throws LambdaConversionException {
	// ...省略代码
	return generateInnerClass();
}


private Class<?> generateInnerClass() throws LambdaConversionException {
	// ...省略代码
	// 生成动态类(这里是LambdaView)
	cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
                 lambdaClassName, null,
                 JAVA_LANG_OBJECT, interfaceNames);
	// 生成构造器
	generateConstructor();

	// Forward the SAM method 用asm生成实现的接口方法
    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName,
                                          interfaceMethodType.toMethodDescriptorString(), null, null);
	// 这里会生成当前类的动态方法lambda$main$0
    new ForwardingMethodGenerator(mv).generate(interfaceMethodType);

	// 对序列化lambda的支持
	if (isSerializable)
            generateSerializationFriendlyMethods();
        else if (accidentallySerializable)
            generateSerializationHostileMethods();

		// 动态类和方法创建完成
        cw.visitEnd();
	final byte[] classBytes = cw.toByteArray();
    // If requested, dump out to a file for debugging purposes
	// 支持dump到指定位置
    if (dumper != null) {
        AccessController.doPrivileged(new PrivilegedAction<>() {
            @Override
            public Void run() {
                dumper.dumpClass(lambdaClassName, classBytes);
                return null;
            }, null,
            new FilePermission("<<ALL FILES>>", "read, write"),
            // createDirectories may need it
            new PropertyPermission("user.dir", "read"));
	}

	try {
        // this class is linked at the indy callsite; so define a hidden nestmate
		// 这里创建隐藏类
        Lookup lookup;
        if (useImplMethodHandle) {
            lookup = caller.defineHiddenClassWithClassData(classBytes, implementation, !disableEagerInitialization,
                                                           NESTMATE, STRONG);
        } else {
            lookup = caller.defineHiddenClass(classBytes, !disableEagerInitialization, NESTMATE, STRONG);
        }
        return lookup.lookupClass();
    }
}

// ForwardingMethodGenerator#generate
void generate(MethodType methodType) {
	// p1: 184(INVOKESTATIC) p2:per/qiao/myutils/lambda/LambdaView p3:lambda$main$0 p4:()V p5:false
	// 这里在当前调用类(LambdaView)生成ambda$main$0方法
	visitMethodInsn(invocationOpcode(), implMethodClassName,
                                implMethodName, implMethodDesc,
                                implClass.isInterface());
}

从方法generateInnerClass中可以看出这里使用了asm字节码技术生成了lambda的动态类(cw.visit(...)), 并且支持dump到文件中(dumper.dumpClass(lambdaClassName, classBytes);), 那么我们将它dump出来。

从方法ForwardingMethodGenerator#generate可以看出用asm在当前类LambdaView中创建了一个名叫lambda$main$0的静态方法(p1为184)

dump路径配置

static {
    final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses";
    String dumpPath = GetPropertyAction.privilegedGetProperty(dumpProxyClassesKey);
}

我这里直接用System.setProperty没有生效, 放到启动命令中才生效

// System.setProperty("jdk.internal.lambda.dumpProxyClasses", "/Users/qiaoyueping/project/myproject/myutils/proxy");
-Djdk.internal.lambda.dumpProxyClasses=/Users/qiaoyueping/project/myproject/myutils/proxy

动态生成的类如下

package per.qiao.myutils.lambda;

// $FF: synthetic class
final class LambdaView$$Lambda$14 implements Runnable {
    private LambdaView$$Lambda$14() {
    }

    public void run() {
        LambdaView.lambda$main$0();
    }
}

它直接调用了LambdaView类中生成的的静态方法lambda$main$0

最后, 验证动态生成的类名和方法

public class LambdaView {

    public static void main(String[] args) {
        // 函数式接口
        Runnable r = () -> {};
        System.out.println(r.getClass().getName());

        Method[] methods = LambdaView.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals("main")) {
                continue;
            }
            System.out.println(method.toGenericString());
        }
    }
}

打印

per.qiao.myutils.lambda.LambdaView$$Lambda$14/0x0000000801001208
private static void per.qiao.myutils.lambda.LambdaView.lambda$main$0()

结论 1.该lambda表达式创建的对象就是jvm根据指令invokedynamic通过动态字节码技术asm动态生成了一个runnable的代理类隐藏类

2.在调用的当前类生成了一个动态方法, 方法名称格式 lambda$当前调用方法名称$动态方法个数(从0开始); 例如当前定义lambda表达式的类叫Test(宿主类), 方法叫say, 那么将在Test类中创建名称为lambda$say$0的动态方法

比较

匿名函数形式和方法引用的lambda的比较

我们看下面两个lambda表达式, func是自定义的表达式代替Function, func2是由HashSet::add直接代替, 那么它们的区别是什么, 下面我们研究一下

public class FuncDiff {
    
    public static void main(String[] args) {
        Function<Integer, Integer> func = x -> x + 1;
        BiFunction<HashSet, String, Boolean> func2 = HashSet::add;
    }
}

同样查看字节码 javap -c FuncDiff.class

public class per.qiao.myutils.lambda.FuncDiff {
  public per.qiao.myutils.lambda.FuncDiff();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
       5: astore_1
       6: invokedynamic #11,  0             // InvokeDynamic #1:apply:()Ljava/util/function/BiFunction;
      11: astore_2
      12: return
}

这里看到在两个lambda表达式的地方同样都有invokedynamic指令生成, 再来看一下生成的动态类

同样在启动命令中添加-Djdk.internal.lambda.dumpProxyClasses=/Users/qiaoyueping/project/myproject/myutils/proxy

func生成的动态类文件


package per.qiao.myutils.lambda;

import java.util.function.Function;

// $FF: synthetic class
final class FuncDiff$$Lambda$14 implements Function {
    private FuncDiff$$Lambda$14() {
    }

    public Object apply(Object var1) {
        return FuncDiff.lambda$main$0((Integer)var1);
    }
}

func2生成的动态类文件

package per.qiao.myutils.lambda;

import java.util.HashSet;
import java.util.function.BiFunction;

// $FF: synthetic class
final class FuncDiff$$Lambda$16 implements BiFunction {
    private FuncDiff$$Lambda$16() {
    }

    public Object apply(Object var1, Object var2) {
        return ((HashSet)var1).add((String)var2);
    }
}

自己看会发现func生成的动态类文件中apply方法里有这么一段return FuncDiff.lambda$main$0((Integer)var1);func2生成的动态类文件中是return ((HashSet)var1).add((String)var2); 一个是静态调用, 一个是实例调用

打印一下FuncDiff中动态生成的方法

public class FuncDiff {

    public static void main(String[] args) {
        Function<Integer, Integer> func = x -> x + 1;
        BiFunction<HashSet, String, Boolean> func2 = HashSet::add;

        Method[] declaredMethods = FuncDiff.class.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            if ("main".equals(declaredMethod.getName())) {
                continue;
            }
            System.out.println("是动态生成的方法吗: " + declaredMethod.isSynthetic());
            System.out.println(declaredMethod.toGenericString());
        }
    }
}

方法签名

是动态生成的方法吗: true
private static java.lang.Integer per.qiao.myutils.lambda.FuncDiff.lambda$main$0(java.lang.Integer)

结论

1.使用匿名函数形式的lambda表达式(x -> x + 1)会生成动态类和动态方法, 而方法引用形式的lambda表达式只会生成动态类,而不会生成动态方法 2.方法引用形式的lambda表达式 表示对现有方法的引用。这里引用了 HashSet 类的 add 方法。

实例方法和静态方法的lambda表达式比较

public class ReferenceLambdaDiff {
    
    public static void main(String[] args) {
        // 静态方法引用式lambda表达式
        BiFunction<HashSet<String>, String, Boolean> func2 = HashSet::add;

        Set<String> set = new HashSet<>();
        // 实例方法引用式lambda表达式
        Function<String, Boolean> func3 = set::add;
    }
}

其实从写法上就可以看出, 静态方法引用式的lambda表达式需要传入一个对象实例, 那么用实例方法引用式的lambda的表达式具体是什么样呢? 我们来探究一下

还是先看字节码

public class per.qiao.myutils.lambda.ReferenceLambdaDiff {
  public per.qiao.myutils.lambda.ReferenceLambdaDiff();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:apply:()Ljava/util/function/BiFunction;
       5: astore_1
       6: new           #11                 // class java/util/HashSet
       9: dup
      10: invokespecial #13                 // Method java/util/HashSet."<init>":()V
      13: astore_2
      14: aload_2
      15: dup
      16: invokestatic  #14                 // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
      19: pop
      20: invokedynamic #20,  0             // InvokeDynamic #1:apply:(Ljava/util/Set;)Ljava/util/function/Function;
      25: astore_3
      26: return
}

同样有invokedynamic指令 直接看生成的动态类 继续在启动命令添加-Djdk.internal.lambda.dumpProxyClasses=/Users/qiaoyueping/project/myproject/myutils/proxy

静态方法引用式的lambda生成的动态文件如下

package per.qiao.myutils.lambda;

import java.util.HashSet;
import java.util.function.BiFunction;

// $FF: synthetic class
final class ReferenceLambdaDiff$$Lambda$14 implements BiFunction {
    private ReferenceLambdaDiff$$Lambda$14() {
    }

    public Object apply(Object var1, Object var2) {
        return ((HashSet)var1).add((String)var2);
    }
}

实例方法引用式的lambda生成的动态文件如下

package per.qiao.myutils.lambda;

import java.util.Set;
import java.util.function.Function;

// $FF: synthetic class
final class ReferenceLambdaDiff$$Lambda$16 implements Function {
    private final Set arg$1;

    private ReferenceLambdaDiff$$Lambda$16(Set var1) {
        this.arg$1 = var1;
    }

    public Object apply(Object var1) {
        return this.arg$1.add((String)var1);
    }
}

结论 1.静态方法引用式的lambda表达式多一个实例参数, 需要在调用的时候传入, 所以它会比实例方法的lambda表达式多一个入参,该入参是第一个参数,并且是调用该方法的实例 2.实例方法引用式的lambda表达式是在创建动态类的时候把实例对象通过构造器传入到动态类中, 调用的时候只需要传入参数即可,就像调用示例对象本身一样 3.静态方法引用式和实力方法引用式都不会生成内部方法, 只会生成动态文件

静态方法lambda

public class StaticMethodLambdaDemo {

    public static void main(String[] args) {
        Function<Object, Object> f = Objects::requireNonNull;
    }

}

看汇编代码

public class per.qiao.myutils.lambda.StaticMethodLambdaDemo {
  public per.qiao.myutils.lambda.StaticMethodLambdaDemo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
       5: astore_1
       6: return
}

少不了的指令invokedynamic

看动态文件 别漏了启动参数-Djdk.internal.lambda.dumpProxyClasses=/Users/qiaoyueping/project/myproject/myutils/proxy

final class StaticMethodLambdaDemo$$Lambda$14 implements Function {
    private StaticMethodLambdaDemo$$Lambda$14() {
    }

    public Object apply(Object var1) {
        return Objects.requireNonNull(var1);
    }
}

它直接调用对象的静态方法, 调用时传入方法参数即可

结论 1.静态方式的lambda表达式调用静态方法, 不需要传入对象实例, 它和静态方式的lambda表达式调用实例方法是一样的; 调用时不需要传对象实例,给参数即可 例如

Set<String> set = new HashSet<>();
Function<String, Boolean> func3 = set::add;

Function<Object, Object> f = Objects::requireNonNull;

lambda的优化

public class LambdaOptimize {

    public static void main(String[] args) {
        Function<Object, Object> func = Objects::requireNonNull;
        Consumer<String> optimizeFunc = Objects::requireNonNull;
    }
}

我们看到一个list的contains方法可以表示两个不同的lambda表达式, 是不是感觉很神奇, 让我们来揭开它的神秘面纱

反编译一下javap -c LambdaOptimize.class

public class per.qiao.myutils.lambda.LambdaOptimize {
  public per.qiao.myutils.lambda.LambdaOptimize();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
       5: astore_1
       6: invokedynamic #11,  0             // InvokeDynamic #1:accept:()Ljava/util/function/Consumer;
      11: astore_2
      12: return
}

从注释0:apply:()Ljava/util/function/Function;#1:accept:()Ljava/util/function/Consumer;也能看出编译器在编译代码的时候推断优化了。

具体来说: • 当方法签名与目标接口的签名匹配时,编译器会自动生成相应的 invokedynamic 指令来处理。 • 在 Consumer 的情况下,requireNonNull 的返回值被自动忽略,因为 Consumer 接口没有返回值。编译后无法直接从字节码中看出这一点,因为这是编译器优化的一部分。

构造器引用式lambda表达式

public class LambdaNew {

    public static void main(String[] args) {
        // 构造器引用式lambda表达式
        // 无参构造
        Supplier<List<Integer>> func4 = ArrayList::new;
        // 有参构造ArrayList(size)
        Function<Integer, List<Integer>> func5 = ArrayList::new;
    }
}

返回值还是保持方法签名的方式, 只是lambda表达式改成了ClassName::new, 和实例方法引用的lanmbda类似(ClassName::methodName)

核心部分字节码如下

 0 invokedynamic #7 <get, BootstrapMethods #0>
 5 astore_1
 6 invokedynamic #11 <apply, BootstrapMethods #1>
11 astore_2
12 return

看一下动态生成的类

final class LambdaNew$$Lambda$14 implements Supplier {
    private LambdaNew$$Lambda$14() {
    }

    public Object get() {
        return new ArrayList();
    }
}


final class LambdaNew$$Lambda$16 implements Function {
    private LambdaNew$$Lambda$16() {
    }

    public Object apply(Object var1) {
        return new ArrayList((Integer)var1);
    }
}

结论 1.构造器lambda格式为ClassName::new 2.具体使用哪个构造器由返回值决定。例如上面的func4使用的无参构造,func5使用的一个参数的构造(ArrayList(size)) 3.构造器形式的lambda表达式也会生成动态类, 只是把实际的创建动作写到了动态类中

序列化 lambda 表达式

每当 lambda 表达式需要序列化时,JVM 会将它封装为一个 SerializedLambda 对象。该对象包含了与 lambda 相关的元数据,例如目标类、方法名、函数接口的签名等。这使得在反序列化时可以恢复 lambda 的行为。通常情况下,SerializedLambda 是 JVM 自动生成和管理的,开发者不常直接与之交互。

我们定义一个函数式接口

@FunctionalInterface
public interface Getter<T, R> extends Serializable {
  R get(T t);
}

打印一下它的方法

public class SerializableLambda {

    public static void main(String[] args) throws Exception {
        Getter<Student, String> getter = Student::getName;
        Method[] declaredMethods = getter.getClass().getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod.toGenericString());
        }
        
        Method[] methods = SerializableLambdaTest.class.getDeclaredMethods();
        for (Method method : methods) {
            if ("main".equals(method.getName())) {
                continue;
            }
            System.out.println(method.toGenericString());
        }
    }
}

打印结果

public java.lang.Object per.qiao.myutils.lambda.SerializableLambda$$Lambda$14/0x0000000801001c00.get(java.lang.Object)
private final java.lang.Object per.qiao.myutils.lambda.SerializableLambda$$Lambda$14/0x0000000801001c00.writeReplace()

private static java.lang.Object per.qiao.myutils.lambda.SerializableLambdaTest.$deserializeLambda$(java.lang.invoke.SerializedLambda)

我们的Getter接口中本身没有一个叫writeReplace的方法, 但是这里多打印了这个方法,那么这个方法是什么?,有什么用?,我们接下来研究它. 在当前类里也多生成了一个方法$deserializeLambda$,入参为SerializedLambda对象

public class SerializableLambda {

    public static void main(String[] args) throws Exception {
        Getter<Student, String> getter = Student::getName;
    }
}

反编译之后的字节码

public class per.qiao.myutils.lambda.SerializableLambdaTest {
  public per.qiao.myutils.lambda.SerializableLambdaTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: invokedynamic #7,  0              // InvokeDynamic #0:get:()Lper/qiao/myutils/lambda/Getter;
       5: astore_1
       6: return
}

动态生成的类如下

package per.qiao.myutils.lambda;

import java.lang.invoke.SerializedLambda;

// $FF: synthetic class
package per.qiao.myutils.lambda;

import java.lang.invoke.SerializedLambda;

// $FF: synthetic class
final class SerializableLambdaTest$$Lambda$14 implements Getter {
    private SerializableLambdaTest$$Lambda$14() {
    }

    public Object get(Object var1) {
        return ((Student)var1).getName();
    }

    private final Object writeReplace() {
        return new SerializedLambda(SerializableLambdaTest.class,
        "per/qiao/myutils/lambda/Getter", 
        "get", 
        "(Ljava/lang/Object;)Ljava/lang/Object;", 
        5, 
        "per/qiao/myutils/lambda/Student", 
        "getName", 
        "()Ljava/lang/String;",
        "(Lper/qiao/myutils/lambda/Student;)Ljava/lang/String;", 
        new Object[0]);
    }
}

除了实现了Getter接口的get方法之外,还多了个final修饰的writeReplace方法, 它创建了个SerializedLambda.

$deserializeLambda$ 对应的字节码如下

  0 aload_0
  1 invokevirtual #16 <java/lang/invoke/SerializedLambda.getImplMethodName : ()Ljava/lang/String;>
  4 astore_1
  5 iconst_m1
  6 istore_2
  7 aload_1
  8 invokevirtual #22 <java/lang/String.hashCode : ()I>
 11 lookupswitch 1
	-75308287:  28 (+17)
	default:  39 (+28)
 28 aload_1
 29 ldc #28 <getName>
 31 invokevirtual #30 <java/lang/String.equals : (Ljava/lang/Object;)Z>
 34 ifeq 39 (+5)
 37 iconst_0
 38 istore_2
 39 iload_2
 40 lookupswitch 1
	0:  60 (+20)
	default:  134 (+94)
 60 aload_0
 61 invokevirtual #34 <java/lang/invoke/SerializedLambda.getImplMethodKind : ()I>
 64 iconst_5
 65 if_icmpne 134 (+69)
 68 aload_0
 69 invokevirtual #37 <java/lang/invoke/SerializedLambda.getFunctionalInterfaceClass : ()Ljava/lang/String;>
 72 ldc #40 <per/qiao/myutils/lambda/Getter>
 74 invokevirtual #42 <java/lang/Object.equals : (Ljava/lang/Object;)Z>
 77 ifeq 134 (+57)
 80 aload_0
 81 invokevirtual #43 <java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodName : ()Ljava/lang/String;>
 84 ldc #46 <get>
 86 invokevirtual #42 <java/lang/Object.equals : (Ljava/lang/Object;)Z>
 89 ifeq 134 (+45)
 92 aload_0
 93 invokevirtual #47 <java/lang/invoke/SerializedLambda.getFunctionalInterfaceMethodSignature : ()Ljava/lang/String;>
 96 ldc #50 <(Ljava/lang/Object;)Ljava/lang/Object;>
 98 invokevirtual #42 <java/lang/Object.equals : (Ljava/lang/Object;)Z>
101 ifeq 134 (+33)
104 aload_0
105 invokevirtual #52 <java/lang/invoke/SerializedLambda.getImplClass : ()Ljava/lang/String;>
108 ldc #55 <per/qiao/myutils/lambda/Student>
110 invokevirtual #42 <java/lang/Object.equals : (Ljava/lang/Object;)Z>
113 ifeq 134 (+21)
116 aload_0
117 invokevirtual #57 <java/lang/invoke/SerializedLambda.getImplMethodSignature : ()Ljava/lang/String;>
120 ldc #60 <()Ljava/lang/String;>
122 invokevirtual #42 <java/lang/Object.equals : (Ljava/lang/Object;)Z>
125 ifeq 134 (+9)
128 invokedynamic #7 <get, BootstrapMethods #0>
133 areturn
134 new #61 <java/lang/IllegalArgumentException>
137 dup
138 ldc #63 <Invalid lambda deserialization>
140 invokespecial #65 <java/lang/IllegalArgumentException.<init> : (Ljava/lang/String;)V>
143 athrow

它对应的java代码大致如下

public Object $deserializeLambda$(SerializedLambda lambda) {
    String methodName = lambda.getImplMethodName();
    int resultIndex = -1; // 默认值

    // 计算哈希值并根据值进行查找
    switch (methodName.hashCode()) {
        case -75308287: // 与 "getName" 的哈希值匹配
            if (methodName.equals("getName")) {
                resultIndex = 0;
            }
            break;
        default:
            break;
    }

    if (resultIndex == 0) {
        // 如果实现方法的类型与期望的类型匹配,返回
        if (lambda.getImplMethodKind() == 5 &&
            lambda.getFunctionalInterfaceClass().equals("per/qiao/myutils/lambda/Getter") &&
            lambda.getFunctionalInterfaceMethodName().equals("get") &&
            lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") &&
            lambda.getImplClass().equals("per/qiao/myutils/lambda/Student") &&
            lambda.getImplMethodSignature().equals("()Ljava/lang/String;")) {
            return Student::getName; // 调用动态方法
        }
    }

    throw new IllegalArgumentException("Invalid lambda deserialization");
}

这里最后return Student::getName又由jvm调用到LambdaMetafactory#altMetafactory方法中去生成动态类

SerializedLambda

/**
  * 构造器; 用于支持在 lambda 表达式序列化时捕获元数据
  * 
  * @param capturingClass    lambda表达式出现的类
  * @param functionalInterfaceClass  函数式接口的类名
  * @param functionalInterfaceMethodName 函数式接口的方法名
  * @param functionalInterfaceMethodSignature    函数式接口方法的签名
  * @param implMethodKind    实现方法的种类(如静态方法、实例方法等)
  * @param implClass 实现方法所在的类
  * @param implMethodName    实现方法的方法名
  * @param implMethodSignature   实现方法的签名
  * @param instantiatedMethodType    实例化的类型签名
  * @param capturedArgs  捕获的参数
  */
public SerializedLambda(Class<?> capturingClass,
                            String functionalInterfaceClass,
                            String functionalInterfaceMethodName,
                            String functionalInterfaceMethodSignature,
                            int implMethodKind,
                            String implClass,
                            String implMethodName,
                            String implMethodSignature,
                            String instantiatedMethodType,
                            Object[] capturedArgs) {
        this.capturingClass = capturingClass;
        this.functionalInterfaceClass = functionalInterfaceClass;
        this.functionalInterfaceMethodName = functionalInterfaceMethodName;
        this.functionalInterfaceMethodSignature = functionalInterfaceMethodSignature;
        this.implMethodKind = implMethodKind;
        this.implClass = implClass;
        this.implMethodName = implMethodName;
        this.implMethodSignature = implMethodSignature;
        this.instantiatedMethodType = instantiatedMethodType;
        this.capturedArgs = Objects.requireNonNull(capturedArgs).clone();
    }

反射调用一下writeReplace方法

public class SerializableLambdaTest {

    public static void main(String[] args) throws Exception {
        Getter<Student, String> getter = Student::getName;
        Method writeReplace = getter.getClass().getDeclaredMethod("writeReplace");
        writeReplace.setAccessible(true);
        SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(getter);
        System.out.println("lambda表达式所在的类: " + serializedLambda.getCapturingClass());   // per/qiao/myutils/lambda/SerializableLambdaTest
        System.out.println("函数接口类路径: " + serializedLambda.getFunctionalInterfaceClass());   // per/qiao/myutils/lambda/Getter
        System.out.println("函数接口中抽象方法的名称: " + serializedLambda.getFunctionalInterfaceMethodName());   // get
        System.out.println("函数接口中抽象方法的签名: " + serializedLambda.getFunctionalInterfaceMethodSignature());   // (Ljava/lang/Object;)Ljava/lang/Object;
        System.out.println("哪个类实现了此函数接口: " + serializedLambda.getImplClass());   // per/qiao/myutils/lambda/Student
        System.out.println("实现此函数接口对应的方法名: " + serializedLambda.getImplMethodName());   // getName
        System.out.println("实现此函数接口对应的方法的签名: " + serializedLambda.getImplMethodSignature());   // ()Ljava/lang/String;
        System.out.println("实现方法类型: " + serializedLambda.getImplMethodKind());   // MethodHandleInfo中定义的常量(例如invokeVirtual/invokeStatic)
        System.out.println("lambda表达式所在的类: " + serializedLambda.getInstantiatedMethodType());   // (Lper/qiao/myutils/lambda/Student;)Ljava/lang/String;
        System.out.println("Lambda表达式可能会用到外部变量个数: " + serializedLambda.getCapturedArgCount());   // 0

    }

打印结果如下

lambda表达式所在的类: per/qiao/myutils/lambda/SerializableLambdaTest
函数接口类路径: per/qiao/myutils/lambda/Getter
函数接口中抽象方法的名称: get
函数接口中抽象方法的签名: (Ljava/lang/Object;)Ljava/lang/Object;
哪个类实现了此函数接口: per/qiao/myutils/lambda/Student
实现此函数接口对应的方法名: getName
实现此函数接口对应的方法的签名: ()Ljava/lang/String;
实现方法类型: 5
lambda表达式所在的类: (Lper/qiao/myutils/lambda/Student;)Ljava/lang/String;
Lambda表达式可能会用到外部变量个数: 0

所以当函数式接口继承序列化接口Serializable后可以通过反射调用lambda表达式的writeReplace方法获取函数式接口和实现者的相关信息, mybatis-plus获取属性名就是通过这种方式获取的。

SerializedLambda的序列化与反序列化

public class SerializableLambdaTest {

    public static void main(String[] args) throws Exception {
        Getter<Student, String> getter = Student::getName;
        System.out.println(getter.getClass().getName());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(getter);
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
        Object target = ois.readObject();
        System.out.println(target.getClass().getName());
    }

打印结果

per.qiao.myutils.lambda.SerializableLambdaTest$$Lambda$14/0x0000000801002000
per.qiao.myutils.lambda.SerializableLambdaTest$$Lambda$30/0x0000000801002220

在运行到Getter<Student, String> getter = Student::getName;时会生成SerializableLambdaTest$$Lambda$14类,dump到文件中 在运行到oos.writeObject(getter)时会调用readResolve方法

借一张图运行图 序列化过程 在序列化的时候会调用writeReplace方法生成SerializedLambda对象, 在反序列化的时候会调用SerializedLambda#readResolve方法

我们再来看一下SerializedLambda#readResolve方法

private Object readResolve() throws ObjectStreamException {
        try {
            @SuppressWarnings("removal")
            Method deserialize = AccessController.doPrivileged(new PrivilegedExceptionAction<>() {
                @Override
                public Method run() throws Exception {
                    Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
                    m.setAccessible(true);
                    return m;
                }
            });

            return deserialize.invoke(null, this);
        }
    }

这两句表明它会调用lambda表达式所在的类(宿主类)生成的$deserializeLambda$方法

Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class);
return deserialize.invoke(null, this);

从上面得知$deserializeLambda$方法会调用Student::getName方法, 继续生成改lambda的动态类,继续dump到文件中, 就可以看到反序列化新生成的SerializableLambdaTest$$Lambda$30

感兴趣的朋友可以看看下面这种写法和函数式接口有什么不同

Function<Student, String> func = (Function<Student, String> & Serializable) Student::getName;

总结 1.当一个lambda表达式继承了序列化接口Serializable后,编译器会在自动生成的动态实现内部加一个writeReplace方法,并且在lambda表达式的宿主类生成一个$deserializeLambda$方法 2.在序列化的时候会调用writeReplace方法,该方法返回一个SerializedLambda对象,该对象用来记录lambada和实现的接口相关的信息. 3.在反序列化的时候会调用SerializedLambda#readResolve方法,继而调用lambda宿主类的$deserializeLambda$方法,然后通过jvm调用LambdaMetafactorymetafactory或者altMetafactory方法动态生成一个新的对象, 也就是反序列化生成的那个对象

参考文章

JDK中Lambda表达式的序列化与SerializedLambda的巧妙使用 Serialize a Lambda in Java