本文已收录 【修炼内功】跃迁之路
初次接触Java8的时候感受Lambda表达式很神奇(Lambda表达式带来的编程新思路),但又总感受它就是匿名类或者内部类的语法糖而已,只是语法上更为简洁罢了,如同如下的代码java
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与匿名类/内部类划等号bootstrap
简单查看Lambda字节码javap -p Lambda
segmentfault
Java编译器自动帮咱们生成了方法lambda$main$0
,咱们有理由相信,Lambda表达式内的逻辑就封装在此函数内app
生成的方法lambda$main$0
又是如何被调用的呢?ide
Lambda的调用使用了invokedynamic
指令,虚拟机视角的方法调用一文中已经详细介绍了invokedynamic
,但这里仍是看不出invokedynamic
指令与lambda$main$0
方法之间究竟是如何关联起来的,invokedynamic
指令的启动函数(BootstrapMethod
)与调用点(CallSite
)又在哪里?函数
其实仔细查看字节码的话能够发下,编译器还会额外生成一个内部类ui
仔细查看内部类的逻辑,是否是像极了虚拟机视角的方法调用一文中所提invokedynamic的运行过程this
- 在第一次执行invokedynamic时,JVM虚拟机会调用该指令所对应的启动方法(
BootstrapMethod
)来生成调用点- 启动方法(
BootstrapMethod
)由方法句柄来指定(MH_BootstrapMethod
)- 启动方法接受三个固定的参数,分别为 Lookup实例、指代目标方法名的字符串及该调用点可以连接的方法句柄类型
- 将调用点绑定至该invokedynamic指令中,以后的运行中虚拟机会直接调用绑定的调用点所连接的方法句柄
为了验证此想法,能够执行java -Djdk.internal.lambda.dumpProxyClasses Lambda
用来导出内部类spa
跟踪内部类的运行能够发现,在执行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中的this指针指向的是周围的类 (定义该Lambda表达式时所处的类) (Lambda表达式里的"陷阱")
因此,lambda表达式确实是语法糖,但并非匿名类/内部类的语法糖