【修煉內功】[Java8] Lambda到底是不是匿名類的語法糖

本文已收錄 【修煉內功】躍遷之路

Lambda到底是不是匿名類的語法糖

初次接觸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);
        }
    }
}

編譯後會產生三個文件編程

sugar_1

雖然從使用效果來看,Lambda與匿名類或者內部類有類似之處(固然也有很大不一樣,如this指針等 Lambda表達式裏的"陷阱"),但從編譯結果來看,並不能簡單地將Lambda與匿名類/內部類劃等號bootstrap

簡單查看Lambda字節碼javap -p Lambdasegmentfault

sugar_2

Java編譯器自動幫咱們生成了方法lambda$main$0,咱們有理由相信,Lambda表達式內的邏輯就封裝在此函數內app

sugar_3

生成的方法lambda$main$0又是如何被調用的呢?ide

sugar_4

Lambda的調用使用了invokedynamic指令,虛擬機視角的方法調用一文中已經詳細介紹了invokedynamic,但這裏仍是看不出invokedynamic指令與lambda$main$0方法之間究竟是如何關聯起來的,invokedynamic指令的啓動函數(BootstrapMethod)與調用點(CallSite)又在哪裏?函數

其實仔細查看字節碼的話能夠發下,編譯器還會額外生成一個內部類ui

sugar_5

仔細查看內部類的邏輯,是否是像極了虛擬機視角的方法調用一文中所提invokedynamic的運行過程this

  1. 在第一次執行invokedynamic時,JVM虛擬機會調用該指令所對應的啓動方法(BootstrapMethod)來生成調用點
  2. 啓動方法(BootstrapMethod)由方法句柄來指定(MH_BootstrapMethod)
  3. 啓動方法接受三個固定的參數,分別爲 Lookup實例、指代目標方法名的字符串及該調用點可以連接的方法句柄類型
  4. 將調用點綁定至該invokedynamic指令中,以後的運行中虛擬機會直接調用綁定的調用點所連接的方法句柄

爲了驗證此想法,能夠執行java -Djdk.internal.lambda.dumpProxyClasses Lambda用來導出內部類spa

sugar_6

跟蹤內部類的運行能夠發現,在執行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文件

sugar_7

對於每個lambda表達式,都會生成一個靜態的私有方法

再查看內部類

sugar_8

只會生成一個內部類,但存在三個方法,每一個方法對應一個lambda私有方法,用以生成對應的調用點綁定到相應的invokedynamic指令上

結合以上咱們能夠總結出:

  • lambda表達式會被編譯爲invokedynamic指令
  • 每個lambda表達式的實現邏輯均會被封裝爲一個靜態私有方法
  • 只要存在lambda表達式調用,便會生成一個內部類
  • 內部類中每個方法(啓動方法 BoostrapMethod)對應一個lambda表達式所生成的靜態私有方法,內部類中的方法用以生成對應的調用點綁定到相應的invokedynamic指令上

這也解釋了爲何lambda中的this指針指向的是周圍的類 (定義該Lambda表達式時所處的類) (Lambda表達式裏的"陷阱")

因此,lambda表達式確實是語法糖,但並非匿名類/內部類的語法糖


訂閱號

相關文章
相關標籤/搜索