Lambda expression

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

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

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

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

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

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

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

 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的對象。ide

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

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表示方法不在源碼中展現。再看一個使用到了成員屬性的表達式:flex

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方法:ui

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的調用。

結語

本文簡單描述了Lambda Expression一小部分的轉換和執行過程,若是有錯誤地方,還望無情指出。
本文其中還未涉及不少細節以及其餘Lambda的特性,好比method reference capture,不僅是一段表達式要被轉換,一個new語句或者方法調用語句也要轉換:

list.filter(String::isEmpty)

還好比Adaptations和Serialization(PS:好吧...這個我沒用到,還不是太瞭解,手動滑稽),再好比Call Site Specifier的解析過程等等等等等......

相關文章
相關標籤/搜索