目录
显示
lambda本质
函数式接口(Consumer
、Function
、Supplier
、Predicate
)的匿名实现类的匿名对象。
源码🧐
首先写一个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中的,原理一样。
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 接口的一个匿名内部类。
总结
- lambda的底层是通过LambdaMetaFactory ,使用asm操作内存,生成实现函数式接口(Function、Consumer)的匿名内部类。
- lambda表达式的本质是一个对象。
- lambda是一个语法糖。