本文已收錄 【修煉內功】躍遷之路
初次接觸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表達式確實是語法糖,但並非匿名類/內部類的語法糖