lambda本质

函数式接口(ConsumerFunctionSupplierPredicate)的匿名实现类的匿名对象。

源码🧐

首先写一个Consumer lambda

   public static void main(String[] args) {  
        List<Integer> list= Arrays.asList(1,2,3,4,5);  
        list.forEach((num)-> System.out.println(num));  
    }

build 后生成 .class文件

InnerClasses:
     public static final #75= #74 of #78; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
  0: #38 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #39 (Ljava/lang/Object;)V
      #40 invokestatic lambda表达式/D_lambda底层原理/Demo.lambda$main$0:(Ljava/lang/Integer;)V
      #41 (Ljava/lang/Integer;)V

可以看到生成了一个InnerClasses 内部类,调用了LambdaMetafactory类的metafactory方法。

然后看一下java.lang.invoke.LambdaMetafactory .metafactory方法:
LambdaMetafactory类

/**
便于创建简单的“函数对象”,这些对象在适当的类型适应和参数的部分计算之后,通过委托给提供的MethodHandle接口来实现一个或多个接口。通常用作调用站点的invokedynamic引导方法,以支持 Java 编程语言的 lambda 表达式和方法引用表达式功能。
这是标准的、精简的元工厂;提供了 altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)额外的灵活性。提供了 above此方法行为的一般说明。
当调用此方法返回的目标CallSite时,生成的函数对象是一个类的实例,该类实现由返回类型 命名invokedType的接口,声明一个方法,其名称由 给出,签名由 给出invokedNamesamMethodType。它还可能覆盖 中的Object其他方法。
形参:
caller – 表示具有调用方辅助功能权限的查找上下文。与 一起使用 invokedynamic时,VM 会自动堆叠。 invokedName – 要实现的方法的名称。与 一起使用 invokedynamic时,它由 NameAndType 结构的 InvokeDynamic 提供,并由 VM 自动堆叠。 invokedType – 预期 CallSite签名 .参数类型表示捕获变量的类型;返回类型是要实现的接口。与 一起使用 invokedynamic时,它由 NameAndType 结构的 InvokeDynamic 提供,并由 VM 自动堆叠。如果实现方法是实例方法,并且此签名具有任何参数,则调用签名中的第一个参数必须与接收方相对应。 samMethodType – 函数对象要实现的方法的签名和返回类型。 implMethod – 描述在调用时应调用的实现方法的直接方法句柄(适当调整参数类型、返回类型,并在调用参数前面附加捕获的参数)。 instantiatedMethodType – 应在调用时动态强制执行的签名和返回类型。这可能与 samMethodType相同,也可能是它的专用化。
返回值:
一个 CallSite,其目标可用于执行捕获,生成由 invokedType
抛出:
LambdaConversionException – 如果违反了所描述 above 的任何链接不变量
**/
public static CallSite metafactory(MethodHandles.Lookup caller,  
                                   String invokedName,  
                                   MethodType invokedType,  
                                   MethodType samMethodType,  
                                   MethodHandle implMethod,  
                                   MethodType instantiatedMethodType)  
        throws LambdaConversionException {  
    AbstractValidatingLambdaMetafactory mf;  
    mf = new InnerClassLambdaMetafactory(caller, invokedType,  
                                         invokedName, samMethodType,  
                                         implMethod, instantiatedMethodType,  
                                         false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);  
    mf.validateMetafactoryArgs();  
    return mf.buildCallSite();  
}

InnerClassLambdaMetafactory 类

/**  
 *通用元工厂构造函数,支持两种标准情况,并允许不常见的选项,如序列化或桥接。
形参:
caller – 由虚拟机自动堆叠;表示具有调用方辅助功能权限的查找上下文。 invokedType – 由虚拟机自动堆叠;调用方法的签名,其中包括返回的 Lambda 对象的预期静态类型,以及捕获的 Lambda 参数的静态类型。如果实现方法是实例方法,则调用签名中的第一个参数将对应于接收器。 samMethodName – 函数接口中要将 lambda 或方法引用转换为的方法的名称,表示为字符串。 samMethodType – 函数接口中要将 lambda 或方法引用转换为的方法类型,表示为 MethodType。 implMethod – 调用生成的函数接口实例的方法时应调用的实现方法(适当调整参数类型、返回类型和对捕获的参数进行调整)。 instantiatedMethodType – 将类型变量替换为捕获站点的实例化后,主要功能接口方法的签名 isSerializable – λ应该可序列化吗?如果设置,则必须扩展 Serializable目标类型或其他 SAM 类型之一。 markerInterfaces – lambda 对象应实现的其他接口。 additionalBridges – 将其他签名桥接到实现方法的方法类型
抛出:
LambdaConversionException – 如果违反了任何元工厂协议不变量
 */
 * public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,  
                                   MethodType invokedType,  
                                   String samMethodName,  
                                   MethodType samMethodType,  
                                   MethodHandle implMethod,  
                                   MethodType instantiatedMethodType,  
                                   boolean isSerializable,  
                                   Class<?>[] markerInterfaces,  
                                   MethodType[] additionalBridges)  
        throws LambdaConversionException {  
    super(caller, invokedType, samMethodName, samMethodType,  
          implMethod, instantiatedMethodType,  
          isSerializable, markerInterfaces, additionalBridges);  
    implMethodClassName = implDefiningClass.getName().replace('.', '/');  
    implMethodName = implInfo.getName();  
    implMethodDesc = implMethodType.toMethodDescriptorString();  
    implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial)  
            ? implDefiningClass  
            : implMethodType.returnType();  
    constructorType = invokedType.changeReturnType(Void.TYPE);  
    lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();  
    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);  
    int parameterCount = invokedType.parameterCount();  
    if (parameterCount > 0) {  
        argNames = new String[parameterCount];  
        argDescs = new String[parameterCount];  
        for (int i = 0; i < parameterCount; i++) {  
            argNames[i] = "arg$" + (i + 1);  
            argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i));  
        }  
    } else {  
        argNames = argDescs = EMPTY_STRING_ARRAY;  
    }  
}

可以看到使用了 asm 写入到字节码文件中

  cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);  

看一下这个cw到底写了什么?

static {  
    final String key = "jdk.internal.lambda.dumpProxyClasses";  
    String path = AccessController.doPrivileged(  
            new GetPropertyAction(key), null,  
            new PropertyPermission(key , "read"));  
    dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);  
}
///...............
///...............
///...............
final byte[] classBytes = cw.toByteArray();
if (dumper != null) {  
    AccessController.doPrivileged(new PrivilegedAction<Void>() {  
        @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"));  
}

可以看到dumper 默认是null,但是当key jdk.internal.lambda.dumpProxyClasses配置时,会将dumper输出。

再次运行项目,在启动命令中加入jdk.internal.lambda.dumpProxyClasses 参数,如下:
我是直接配置在idea中的,原理一样。
image.png

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2023/10/25     17:38            468 Demo$$Lambda$1.class
-a----        2023/10/25     17:33           1602 Demo.class

多出一个 Demo$$Lambda$1.class 文件
反编译看一下:

// $FF: synthetic class  
final class Demo$$Lambda$1 implements Consumer {  
    private Demo$$Lambda$1() {  
    }  

    @Hidden  
    public void accept(Object var1) {  
        Demo.lambda$main$0((Integer)var1);  
    }  
}

可以看到,实际上是中间代码生成了一个实现Consumer 接口的一个匿名内部类。

总结

image.png
💡

  • lambda的底层是通过LambdaMetaFactory ,使用asm操作内存,生成实现函数式接口(Function、Consumer)的匿名内部类。
  • lambda表达式的本质是一个对象。
  • lambda是一个语法糖。