lamda expression

java裏的Lambda Expression想必你們都已經很清楚是個什麼東西了。Lambda Expression就是被解析爲functional interface的實現類,實現了它有且僅有的一個抽象方法(exactly one abstract method)。javascript

Expression在java中提出是在JSR 335中,那麼天然而然,要實現這個玩意兒就會有幾種方法,好比說內部類,動態代理之類的。可是這裏有兩個重要目標:php

1.maximizing flexibility for future optimization by not committing to a specific strategy;2.providing stability in the classfile representation.html

這兩個目標看起來彷佛互不兼容,可是使用invokedynamic instruction來實現,就能夠完美解決。Lambda Expression依賴了一些JSR 292中的特性,好比invokedynamic和method handles等等...java

接着就簡單看看如何用invokedynamics實現Lambda。別說了,先來幾個術語壓壓驚:
1.dynamic call site
程序中出現lambda的地方都被稱做dynamic call site,例如:bootstrap

new Thread(() -> { System.out.printf("Im a stateless lambda"); }).start(); 

2.bootstrap method
java裏對全部Lambda的有統一的bootstrap方法(LambdaMetafactory.metafactory):app

public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType) 

bootstrap運行期動態生成了匿名類,將其與CallSite綁定,獲得了一個獲取匿名類實例的call site object。
3.call site object
call site object持有MethodHandle的引用做爲它的target,它是bootstrap method方法成功調用後的結果,將會與 dynamic call site永久綁定。call site object的target會被JVM執行,就如同執行一條invokevirtual指令,其所需的參數也會被壓入operand stack。最後會得一個實現了functional interface的對象。less

夠了夠了,直接來看Lambda在編譯期作的事兒。首先會有個desugar的操做,就是把Lambda body轉換成一個方法,方法有跟Lambda Expression對應的參數和返回值(可能會有額外的參數,好比你用到了外部變量)。生成的方法就叫作desugared method,方法大致能夠分爲兩種類型:一種叫non-instance-capturing,意思是表達式裏沒有用到外部引用(this, super或者外部類的成員屬性),這種會被轉換爲私有的靜態方法;另一種就是instance-capturing,跟上一個正好相反,會被轉換成私有的實例方法。好比說這裏有個表達式:ide

public static void main(String[] args) throws InterruptedException { new Thread(() -> { System.out.printf("Im a stateless lambda"); }).start(); Thread.sleep(100); } 

編譯以後就會在同類生成對應的non-instance-capturing方法:函數

private static synthetic lambda$main$0()V 方法體省略。。。 

synthetic flag表示方法不在源碼中展現。再看一個使用到了成員屬性的表達式:工具

public class CapturedLambda{ …… public String captureValue(){ String prefix="weirdness"; return getValue(arg -> arg+prefix+name,"1"); } private <T extends String> T getValue(Function func,String num){ return (T) func.apply(num); } …… } 

編譯以後就會在同類生成對應的instance-capturing方法:

private synthetic lambda$captureValue$0(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; 方法體省略。。。 

就拿這個captureValue方法裏面的表達式來講,在這裏會生成invokedynamic 指令:

INVOKEDYNAMIC apply(Llambda/CapturedLambda;Ljava/lang/String;)Ljava/util/function/Function; [ // handle kind 0x6 : 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; // arguments: (Ljava/lang/Object;)Ljava/lang/Object;, // handle kind 0x7 : INVOKESPECIAL lambda/CapturedLambda.lambda$captureValue$0(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/Object; ] 

在這裏invokedynamic所須要的參數是兩個在run-time constant pool裏的索引,它倆合起來指向的是一個CONSTANT_InvokeDynamic_info結構,在這裏工具幫我展開了。而後結合以前的metafactory方法看一下參數的對應:

  1. MethodHandles.Lookup caller表明了一個能訪問調用者的lookup context(在這裏調用者是CapturedLambda)這個參數在調用時由VM自動壓棧;
  2. String invokedName表示要實現的方法名,在這裏就是Function的apply方法,調用時由VM自動壓棧;
  3. MethodType invokedType告知了上面call site object它所持有的MethodHandle須要的參數和返回類型(signature),在這裏是(Llambda/CapturedLambda;Ljava/lang/String;)Ljava/util/function/Function由於captureValue方法裏面的表達式使用到了一個局部變量prefix和成員變量name因此有這2個參數,調用時由VM自動壓棧;
  4. MethodType samMethodType表示要實現functional interface裏面抽象方法的類型,在這裏是Function的apply方法,即(Ljava/lang/Object;)Ljava/lang/Object;
  5. MethodHandle implMethod表示要調用的desugared method,這裏就是lambda/CapturedLambda.lambda$captureValue$0(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;
  6. MethodType instantiatedMethodType即運行時的類型,由於方法定義多是泛型,傳入時多是具體類型String之類的,要作類型校驗強轉等等,能夠與MethodType samMethodType相同,這裏是同樣的,都是 (Ljava/lang/Object;)Ljava/lang/Object

接着,就進入運行期了。VM首先將上述參數壓棧,調用bootstrap method,簡單看一下此方法裏作了什麼:

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(); } 

它new了一個InnerClassLambdaMetafactory實例,而後返回對應的CallSite對象,接着看InnerClassLambdaMetafactory:

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); ………… 

作了一些爲用ASM生成匿名類所要用到的字段,類名,接口名等等的一些準備工做,而後回來看buildCallSite方法:

CallSite buildCallSite() throws LambdaConversionException { final Class<?> innerClass = spinInnerClass(); ………… } 

spinInnerClass方法是具體用ASM組裝匿名類的地方,我不想貼這麼多代碼。。。能省略仍是省略吧:

private Class<?> spinInnerClass() throws LambdaConversionException { String[] interfaces; ………… cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName, null, JAVA_LANG_OBJECT, interfaces); // Generate final fields to be filled in by constructor for (int i = 0; i < argDescs.length; i++) { FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argDescs[i], null, null); fv.visitEnd(); } ………… 

在這裏生成了頭部信息,即類名(根據它的生成規則,在文中的例子裏是CapturedLambda$$Lambda$1),還有要實現的接口(這裏是Function接口)還有訪問標識亂七八糟什麼的,接着生成實例字段。ASM的文檔能夠看ASM API。繼續看~

…………
generateConstructor();

        if (invokedType.parameterCount() != 0) { generateFactory(); } // Forward the SAM method MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName, samMethodType.toMethodDescriptorString(), null, null); mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); new ForwardingMethodGenerator(mv).generate(samMethodType); ………… 

生成私有構造方法和若有必要生成工廠方法。在文中這個例子裏須要生成工廠方法,由於用到了外部變量,這個是須要運行時傳過來的,但好比是生成的是non-instance-capturing方法就不須要,由於沒用到外部變量直接用構造函數。接着看~

…………
cw.visitEnd();

        // Define the generated class in this VM. final byte[] classBytes = cw.toByteArray(); // If requested, dump out to a file for debugging purposes 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")); } return UNSAFE.defineAnonymousClass(targetClass, classBytes, null); 

「生」完收工~ 最後用UNSAFE.defineAnonymousClass加載了類,這個方法每次都會返回不一樣的類。還有,看到了dumper這是爲了調試用的,能夠將生成了類保存到指定目錄下,經過-Djdk.internal.lambda.dumpProxyClasses=目錄

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); } 

ok,再回到buildCallSite方法看一下餘下的工做:

…………
      if (invokedType.parameterCount() == 0) { final Constructor<?>[] ctrs = AccessController.doPrivileged( new PrivilegedAction<Constructor<?>[]>() { @Override public Constructor<?>[] run() { Constructor<?>[] ctrs = innerClass.getDeclaredConstructors(); if (ctrs.length == 1) { // The lambda implementing inner class constructor is private, set // it accessible (by us) before creating the constant sole instance ctrs[0].setAccessible(true); } return ctrs; } }); if (ctrs.length != 1) { throw new LambdaConversionException("Expected one lambda constructor for " + innerClass.getCanonicalName() + ", got " + ctrs.length); } try { Object inst = ctrs[0].newInstance(); return new ConstantCallSite(MethodHandles.constant(samBase, inst)); } catch (ReflectiveOperationException e) { throw new LambdaConversionException("Exception instantiating lambda object", e); } } else { try { UNSAFE.ensureClassInitialized(innerClass); return new ConstantCallSite( MethodHandles.Lookup.IMPL_LOOKUP .findStatic(innerClass, NAME_FACTORY, invokedType)); } catch (ReflectiveOperationException e) { throw new LambdaConversionException("Exception finding constructor", e); } } 

根據有沒有構造函數參數來建立不一樣的CallSite,若是有直接將構造函數包裝成MethodHandle做爲CallSite的target,不然就運用
Lookup來查找工廠方法做爲target。最後看一下生成的lambda class:

final class CapturedLambda$$Lambda$1 implements Function { private final CapturedLambda arg$1; private final String arg$2; private CapturedLambda$$Lambda$1(CapturedLambda var1, String var2) { this.arg$1 = var1; this.arg$2 = var2; } private static Function get$Lambda(CapturedLambda var0, String var1) { return new CapturedLambda$$Lambda$1(var0, var1); } @Hidden public Object apply(Object var1) { return this.arg$1.lambda$captureValue$0(this.arg$2, var1); } } 

實現了Function接口,構造函數接收了外部引用,有剛纔說的綁定到CallSite的工廠方法,實現的apply調用的以前編譯器生成的instance-capturing方法:

private synthetic lambda$captureValue$0(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; 方法體省略。。。 

在invokedynamic調用完成獲得CapturedLambda$$Lambda$1實例以後,就能夠完成最後一步操做:

private getValue(Ljava/util/function/Function;Ljava/lang/String;)Ljava/lang/String; L0 LINENUMBER 27 L0 ALOAD 1 ALOAD 2 INVOKEINTERFACE java/util/function/Function.apply (Ljava/lang/Object;)Ljava/lang/Object; ………… 

經過INVOKEINTERFACE 來完成對functional interface object的調用。

相關文章
相關標籤/搜索