共计 4037 个字符,预计需要花费 11 分钟才能阅读完成。
本文已收录【修炼内功】跃迁之路
初次接触 Java8 的时候感觉 Lambda 表达式很神奇(Lambda 表达式带来的编程新思路),但又总感觉它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同以下的代码
public class Lambda {private static void hello(String name, Consumer<String> printer) {printer.accept(name);
}
public static void main(String[] args) {hello("lambda", (name) -> System.out.println("Hello" + name));
hello("匿名类", new Consumer<String> () {
@Override
public void accept(String name) {System.out.println("Hello" + name);
}
});
hello("内部类", new SupplierImpl());
}
static class SupplierImpl implements Consumer<String> {
@Override
public void accept(String name) {System.out.println("Hello" + name);
}
}
}
编译后会产生三个文件
虽然从使用效果来看,Lambda 与匿名类或者内部类有相似之处(当然也有很大不同,如 this 指针等 Lambda 表达式里的 ” 陷阱 ”),但从编译结果来看,并不能简单地将 Lambda 与匿名类 / 内部类划等号
简单查看 Lambda 字节码javap -p Lambda
Java 编译器自动帮我们生成了方法lambda$main$0
,我们有理由相信,Lambda 表达式内的逻辑就封装在此函数内
生成的方法 lambda$main$0
又是如何被调用的呢?
Lambda 的调用使用了 invokedynamic
指令,虚拟机视角的方法调用一文中已经详细介绍了 invokedynamic
,但这里还是看不出invokedynamic
指令与 lambda$main$0
方法之间到底是如何关联起来的,invokedynamic
指令的启动函数 (BootstrapMethod
) 与调用点 (CallSite
) 又在哪里?
其实仔细查看字节码的话可以发下,编译器还会额外生成一个内部类
仔细查看内部类的逻辑,是不是像极了虚拟机视角的方法调用一文中所提 invokedynamic 的运行过程
- 在第一次执行 invokedynamic 时,JVM 虚拟机会调用该指令所对应的启动方法 (
BootstrapMethod
) 来生成调用点- 启动方法 (
BootstrapMethod
) 由方法句柄来指定(MH_BootstrapMethod
)- 启动方法接受三个固定的参数,分别为 Lookup 实例、指代目标方法名的字符串及该调用点能够链接的方法句柄类型
- 将调用点绑定至该 invokedynamic 指令中,之后的运行中虚拟机会直接调用绑定的调用点所链接的方法句柄
为了验证此想法,可以执行 java -Djdk.internal.lambda.dumpProxyClasses Lambda
用来导出内部类
跟踪内部类的运行可以发现,在执行 lambda 表达式的时候会调用 MethodHandleNatives.linkCallSite
方法来生成并链接到调用点
// Up-calls from the JVM.
// These must NOT be public.
/**
* The JVM is linking an invokedynamic instruction. Create a reified call site for it.
*/
static MemberName linkCallSite(Object callerObj,
Object bootstrapMethodObj,
Object nameObj, Object typeObj,
Object staticArguments,
Object[] appendixResult) {MethodHandle bootstrapMethod = (MethodHandle)bootstrapMethodObj;
Class<?> caller = (Class<?>)callerObj;
String name = nameObj.toString().intern();
MethodType type = (MethodType)typeObj;
if (!TRACE_METHOD_LINKAGE)
return linkCallSiteImpl(caller, bootstrapMethod, name, type,
staticArguments, appendixResult);
return linkCallSiteTracing(caller, bootstrapMethod, name, type,
staticArguments, appendixResult);
}
static MemberName linkCallSiteImpl(Class<?> caller,
MethodHandle bootstrapMethod,
String name, MethodType type,
Object staticArguments,
Object[] appendixResult) {
CallSite callSite = CallSite.makeSite(bootstrapMethod,
name,
type,
staticArguments,
caller);
if (callSite instanceof ConstantCallSite) {appendixResult[0] = callSite.dynamicInvoker();
return Invokers.linkToTargetMethod(type);
} else {appendixResult[0] = callSite;
return Invokers.linkToCallSiteMethod(type);
}
}
caller
为 调用 lambda方法的类Lambda
[Class]
bootstrapMethod
为启动方法的句柄 [MethodHandler]
name
为 lambda 表达式实际类型中需要执行的方法名acccept
(Consumer.accept)
type
为生成的方法类型()Consumer
[MethodType]
staticArguments
中包含了 lambda 方法的方法句柄 [MethodHandler] 及方法类型 [MethodType]
CallSite.makeSite
方法会生成调用点,最终调用如 class 文件中所示的 LambdaMetafactory.metafactory
方法
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();}
简单来将,所生成内部类作用,是为了生成调用点,并链接到 invokedynamic 指令,以便动态调用
如果一个类中,多次使用 lambda 表达式,会生成多少个方法,又会生成多少个内部类?
public class Lambda {private static void hello(String name, Consumer<String> printer) {printer.accept(name);
}
public static void main(String[] args) {hello("lambda1", (name) -> System.out.println("Hello" + name));
hello("lambda2", (name) -> System.out.println("Hello" + name));
new Thread(() -> {System.out.println("thread");
}).start();}
}
上述示例中共使用了三处、两种(Consumer、Runnable)Lambda 表达式,编译后查看 class 文件
对于每一个 lambda 表达式,都会生成一个静态的私有方法
再查看内部类
只会生成一个内部类,但存在三个方法,每个方法对应一个 lambda 私有方法,用以生成对应的调用点绑定到相应的 invokedynamic 指令上
结合以上我们可以总结出:
- lambda 表达式会被编译为 invokedynamic 指令
- 每一个 lambda 表达式的实现逻辑均会被封装为一个静态私有方法
- 只要存在 lambda 表达式调用,便会生成一个内部类
- 内部类中每一个方法对应一个 lambda 表达式所生成的静态私有方法,内部类中的方法用以生成对应的调用点绑定到相应的 invokedynamic 指令上
这也解释了为什么 lambda 中的 this 指针指向的是 周围的类 (定义该 Lambda 表达式时所处的类) (Lambda 表达式里的 ” 陷阱 ”)
所以,lambda 表达式确实是语法糖,但并不是匿名类 / 内部类的语法糖