lambda表達(dá)式實(shí)現(xiàn)原理分析

本文將深入了解lambda表達(dá)式的實(shí)現(xiàn)原理和這樣的實(shí)現(xiàn)方案的取舍權(quán)衡馋劈。

Java為什么需要lambda表達(dá)式娶吞?

能夠提升代碼簡(jiǎn)潔性、提高代碼可讀性械姻。

例如妒蛇,在平時(shí)的開發(fā)過(guò)程中,把一個(gè)列表轉(zhuǎn)換成另一個(gè)列表或map等等這樣的轉(zhuǎn)換操作是一種常見需求楷拳。 在沒(méi)有l(wèi)ambda之前通常都是這樣實(shí)現(xiàn)的绣夺。

List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = new ArrayList<>();
for (long id : idList) {
    personList.add(getById(id));
}

代碼重復(fù)多了之后,大家就會(huì)對(duì)這種常見代碼進(jìn)行抽象欢揖,形成一些類庫(kù)便于復(fù)用。 上面的需求可以抽象成:對(duì)一個(gè)列表中的每個(gè)元素調(diào)用一個(gè)轉(zhuǎn)換函數(shù)轉(zhuǎn)換并輸出結(jié)果列表。

interface Function {
    <T, R> R fun(T input);
}
<T, R> List<R> map(List<T> inputList, Function function) {
    List<R> mappedList = new ArrayList<>();
    for (T t : inputList) {
        mappedList.add(function.fun(t));
    }
    return mappedList;
}

有了這個(gè)抽象,最開始的代碼便可以”簡(jiǎn)化”成

List<Long> idList = Arrays.asList(1L, 2L, 3L);
List<Person> personList = map(idList, new Function<Long, Person>() {
    @Override
    public Person fun(Long input) {
        return getById(input);
    }
});

雖然實(shí)現(xiàn)邏輯少了一些,但是同樣也遺憾地發(fā)現(xiàn)拯坟,代碼行數(shù)還變多了似枕。
因?yàn)镴ava語(yǔ)言中函數(shù)并不能作為參數(shù)傳遞到方法中,函數(shù)只能寄存在一個(gè)類中表示侯嘀。為了能夠把函數(shù)作為參數(shù)傳遞到方法中献汗,我們被迫使用了匿名內(nèi)部類實(shí)現(xiàn)尿招,需要加相當(dāng)多的冗余代碼。
在一些支持函數(shù)式編程的語(yǔ)言(Functional Programming Language)中(例如Python, Scala, Kotlin等),函數(shù)是一等公民菩帝,函數(shù)可以成為參數(shù)傳遞以及作為返回值返回简烘。 例如在Kotlin中岖妄,上述的代碼可以縮減到很短忧换,代碼只包含關(guān)鍵內(nèi)容,沒(méi)有冗余信息勤篮。

val personList = idList.map { id -> getById(id) }

這樣的編寫效率差距也導(dǎo)致了一部分Java用戶流失到其他語(yǔ)言统捶,不過(guò)最終終于在JDK8也提供了Lambda表達(dá)式能力恨豁,來(lái)支持這種函數(shù)傳遞。

List<Person> personList = map(idList, input -> getById(input));

Lambda表達(dá)式只是匿名內(nèi)部類的語(yǔ)法糖嗎摸恍?

如果要在Java語(yǔ)言中實(shí)現(xiàn)lambda表達(dá)式嵌巷,初步觀察胃珍,通過(guò)javac把這種箭頭語(yǔ)法還原成匿名內(nèi)部類,就可以輕松實(shí)現(xiàn),因?yàn)樗鼈児δ芑臼堑葍r(jià)的(IDEA中經(jīng)常有提示)胖眷。

但是匿名內(nèi)部類有一些缺點(diǎn)憔儿。

  1. 每個(gè)匿名內(nèi)部類都會(huì)在編譯時(shí)創(chuàng)建一個(gè)對(duì)應(yīng)的class冯挎,并且是有文件的,因此在運(yùn)行時(shí)不可避免的會(huì)有加載蜡峰、驗(yàn)證秒啦、準(zhǔn)備肥惭、解析、初始化的類加載過(guò)程紊搪。
  2. 每次調(diào)用都會(huì)創(chuàng)建一個(gè)這個(gè)匿名內(nèi)部類class的實(shí)例對(duì)象蜜葱,無(wú)論是有狀態(tài)的(capturing,從上下文中捕獲一些變量)還是無(wú)狀態(tài)(non-capturing)的內(nèi)部類耀石。

invokedynamic介紹

如果有一種函數(shù)引用牵囤、指針就好了,但JVM中并沒(méi)有函數(shù)類型表示娶牌。 Java中有表示函數(shù)引用的對(duì)象嗎奔浅,反射中有個(gè)Method對(duì)象馆纳,但它的問(wèn)題是性能問(wèn)題诗良,每次執(zhí)行都會(huì)進(jìn)行安全檢查,且參數(shù)都是Object類型鲁驶,需要boxing等等鉴裹。

還有其他表示函數(shù)引用的方法嗎?MethodHandle钥弯,它是在JDK7中與invokedynamic指令等一起提供的新特性径荔。

但直接使用MethodHandle來(lái)實(shí)現(xiàn),由于沒(méi)有簽名信息脆霎,會(huì)遇不能重載的問(wèn)題总处。并且MethodHandle的invoke方法性能不一定能保證比字節(jié)碼調(diào)用好。

invokedynamic出現(xiàn)的背景

JVM上的動(dòng)態(tài)語(yǔ)言(JRuby, Scala等)睛蛛,要實(shí)現(xiàn)dynamic typing動(dòng)態(tài)類型鹦马,是比較麻煩的。
這里簡(jiǎn)單解釋一下什么是dynamic typing忆肾,與其相對(duì)的是static typing靜態(tài)類型荸频。
static typing: 所有變量的類型在編譯時(shí)都是確定的,并且會(huì)進(jìn)行類型檢查客冈。 dynamic typing: 變量的類型在編譯時(shí)不能確定旭从,只能在運(yùn)行時(shí)才能確定、檢查场仲。

例如如下動(dòng)態(tài)語(yǔ)言的例子和悦,a和b的類型都是未知的,因此a.append(b)這個(gè)方法是什么也是未知的渠缕。

def add(val a, val b)
    a.append(b)

而在Java中a和b的類型在編譯時(shí)就能確定摹闽。

SimpleString add(SimpleString a, SimpleString b) {
    return a.append(b);
}

編譯后的字節(jié)碼如下,通過(guò)invokevirtual明確調(diào)用變量a的函數(shù)簽名為(LSimpleString;)LSimpleString;的方法褐健。

0: aload_1
1: aload_2
2: invokevirtual #2 // Method SimpleString.append:(LSimpleString;)LSimpleString;
5: areturn

關(guān)于方法調(diào)用的字節(jié)碼指令付鹿,JVM中提供了四種澜汤。
invokestatic - 調(diào)用靜態(tài)方法
invokeinterface - 調(diào)用接口方法
invokevirtual - 調(diào)用實(shí)例非接口方法的public方法
invokespecial - 其他的方法調(diào)用,private舵匾,constructor, super 這幾種方法調(diào)用指令俊抵,在編譯的時(shí)候就已經(jīng)明確指定了要調(diào)用什么樣的方法,且均需要接收一個(gè)明確的常量池中的方法的符號(hào)引用坐梯,并進(jìn)行類型檢查徽诲,是不能隨便傳一個(gè)不滿足類型要求的對(duì)象來(lái)調(diào)用的,即使傳過(guò)來(lái)的類型中也恰好有一樣的方法簽名也不行吵血。

invokedynamic功能

這個(gè)限制讓JVM上的動(dòng)態(tài)語(yǔ)言實(shí)現(xiàn)者感到很艱難谎替,只能暫時(shí)通過(guò)性能較差的反射等方式實(shí)現(xiàn)動(dòng)態(tài)類型。
這說(shuō)明在字節(jié)碼層面無(wú)法支持動(dòng)態(tài)分派蹋辅,該怎么辦呢钱贯,又用到了大家熟悉的”All problems in computer science can be solved by another level of indirection”了。 要實(shí)現(xiàn)動(dòng)態(tài)分派侦另,既然不能在編譯時(shí)決定秩命,那么我們把這個(gè)決策推遲到運(yùn)行時(shí)再?zèng)Q定,由用戶的自定義代碼告訴給JVM要執(zhí)行什么方法褒傅。

在jdk7弃锐,Java提供了invokedynamic指令來(lái)解決這個(gè)問(wèn)題,同時(shí)搭配的還有java.lang.invoke包殿托。 這個(gè)指令大部分用戶不太熟悉霹菊,因?yàn)椴幌駃nvokestatic等指令,它在Java語(yǔ)言中并沒(méi)有和它相關(guān)的直接概念支竹。

關(guān)鍵的概念有如下幾個(gè)

  1. invokedynamic指令: 運(yùn)行時(shí)JVM第一次到這里的時(shí)候會(huì)進(jìn)行l(wèi)inkage旋廷,會(huì)調(diào)用用戶指定的bootstrap method來(lái)決定要執(zhí)行什么方法,之后便不需要這個(gè)解析步驟唾戚。這個(gè)invokedynamic指令出現(xiàn)的地方也叫做dynamic call site
  2. Bootstrap Method: 用戶可以自己編寫的方法柳洋,實(shí)現(xiàn)自己的邏輯最終返回一個(gè)CallSite對(duì)象。
  3. CallSite: 負(fù)責(zé)通過(guò)getTarget()方法返回MethodHandle
  4. MethodHandle: MethodHandle表示的是要執(zhí)行的方法的指針

再串聯(lián)起來(lái)梳理下

invokedynamic在最開始時(shí)處于未鏈接(unlinked)狀態(tài)叹坦,這時(shí)這個(gè)指令并不知道要調(diào)用的目標(biāo)方法是什么熊镣。
當(dāng)JVM要第一次執(zhí)行某個(gè)地方的invokedynamic指令的時(shí)候,invokedynamic必須先進(jìn)行鏈接(linkage)募书。
鏈接過(guò)程通過(guò)調(diào)用一個(gè)boostrap method绪囱,傳入當(dāng)前的調(diào)用相關(guān)信息,bootstrap method會(huì)返回一個(gè)CallSite莹捡,這個(gè)CallSite中包含了MethodHandle的引用鬼吵,也就是CallSite的target。
invokedynamic指令便鏈接到這個(gè)CallSite上篮赢,并把所有的調(diào)用delegate到它當(dāng)前的targetMethodHandle上齿椅。根據(jù)target是否需要變換琉挖,CallSite可以分為MutableCallSiteConstantCallSiteVolatileCallSite等涣脚,可以通過(guò)切換target MethodHandle實(shí)現(xiàn)動(dòng)態(tài)修改要調(diào)用的方法示辈。

lambda表達(dá)式真正是如何實(shí)現(xiàn)的

下面直接看一下目前java實(shí)現(xiàn)lambda的方式

以下面的代碼為例

public class RunnableTest {
    void run() {
        Function<Integer, Integer> function = input -> input + 1;
        function.apply(1);
    }
}

編譯后通過(guò)javap查看生成的字節(jié)碼

void run();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
         5: astore_1
         6: aload_1
         7: iconst_1
         8: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: invokeinterface #4,  2            // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        16: pop
        17: return
      LineNumberTable:
        line 12: 0
        line 13: 6
        line 14: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  this   Lcom/github/liuzhengyang/invokedyanmic/RunnableTest;
            6      12     1 function   Ljava/util/function/Function;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6      12     1 function   Ljava/util/function/Function<Ljava/lang/Integer;Ljava/lang/Integer;>;

private static java.lang.Integer lambda$run$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #5                  // Method java/lang/Integer.intValue:()I
         4: iconst_1
         5: iadd
         6: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: areturn
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0 input   Ljava/lang/Integer;

對(duì)應(yīng)Function<Integer, Integer> function = input -> input + 1;這一行的字節(jié)碼為

0: invokedynamic #2,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
5: astore_1

這里再?gòu)?fù)習(xí)一下invokedynamic的步驟毛好。

  1. JVM第一次解析時(shí)劫窒,調(diào)用用戶定義的bootstrap method
  2. bootstrap method會(huì)返回一個(gè)CallSite
  3. CallSite中能夠得到MethodHandle,表示方法指針
  4. JVM之后調(diào)用這里就不再需要重新解析波岛,直接綁定到這個(gè)CallSite上芭梯,調(diào)用對(duì)應(yīng)的target MethodHandle险耀,并能夠進(jìn)行inline等調(diào)用優(yōu)化

第一行invokedynamic后面有兩個(gè)參數(shù),第二個(gè)0沒(méi)有意義固定為0 第一個(gè)參數(shù)是#2玖喘,指向的是常量池中類型為CONSTANT_InvokeDynamic_info的常量甩牺。

#2 = InvokeDynamic      #0:#32         // #0:apply:()Ljava/util/function/Function;

這個(gè)常量對(duì)應(yīng)的#0:#32中第二個(gè)#32表示的是這個(gè)invokedynamic指令對(duì)應(yīng)的動(dòng)態(tài)方法的名字和方法簽名(方法類型)

#32 = NameAndType        #43:#44        // apply:()Ljava/util/function/Function;

第一個(gè)#0表示的是bootstrap method在BootstrapMethods表中的索引。在javap結(jié)果的最后看到是

BootstrapMethods:
  0: #28 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;
    Method arguments:
      #29 (Ljava/lang/Object;)Ljava/lang/Object;
      #30 invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
      #31 (Ljava/lang/Integer;)Ljava/lang/Integer;

再看下BootstrapMethods屬性對(duì)應(yīng)JVM虛擬機(jī)規(guī)范里的說(shuō)明芒涡。

BootstrapMethods_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 num_bootstrap_methods;
    {   u2 bootstrap_method_ref;
        u2 num_bootstrap_arguments;
        u2 bootstrap_arguments[num_bootstrap_arguments];
    } bootstrap_methods[num_bootstrap_methods];
}

bootstrap_method_ref
The value of the bootstrap_method_ref item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_MethodHandle_info structure

bootstrap_arguments[]
Each entry in the bootstrap_arguments array must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_String_info, CONSTANT_Class_info, CONSTANT_Integer_info, CONSTANT_Long_info, CONSTANT_Float_info, CONSTANT_Double_info, CONSTANT_MethodHandle_info, or CONSTANT_MethodType_info structure

CONSTANT_MethodHandle_info The CONSTANT_MethodHandle_info structure is used to represent a method handle

這個(gè)BootstrapMethod屬性可以告訴invokedynamic指令需要的boostrap method的引用以及參數(shù)的數(shù)量和類型柴灯。 #28對(duì)應(yīng)的是bootstrap_method_ref卖漫,為

#28 = MethodHandle       #6:#40         // 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;

按照J(rèn)VM規(guī)范费尽,BootstrapMethod接收3個(gè)標(biāo)準(zhǔn)參數(shù)和一些自定義參數(shù),標(biāo)準(zhǔn)參數(shù)如下

  1. MethodHandles.$Lookup類型的caller參數(shù)羊始,這個(gè)對(duì)象能夠通過(guò)類似反射的方式拿到在執(zhí)行invokedynamic指令這個(gè)環(huán)境下能夠調(diào)動(dòng)到的方法旱幼,比如其他類的private方法是調(diào)用不到的。這個(gè)參數(shù)由JVM來(lái)入棧
  2. String類型的invokedName參數(shù)突委,表示invokedynamic要實(shí)現(xiàn)的方法的名字柏卤,在這里是apply,是lambda表達(dá)式實(shí)現(xiàn)的方法名匀油,這個(gè)參數(shù)由JVM來(lái)入棧
  3. MethodType類型的invokedType參數(shù)缘缚,表示invokedynamic要實(shí)現(xiàn)的方法的類型,在這里是()Function敌蚜,這個(gè)參數(shù)由JVM來(lái)入棧

#29,#30,#31是可選的自定義參數(shù)類型

#29 = MethodType         #41            //  (Ljava/lang/Object;)Ljava/lang/Object;
#30 = MethodHandle       #6:#42         // invokestatic com/github/liuzhengyang/invokedyanmic/RunnableTest.lambda$run$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
#31 = MethodType         #21            //  (Ljava/lang/Integer;)Ljava/lang/Integer;

通過(guò)java.lang.invoke.LambdaMetafactory#metafactory的代碼說(shuō)明下

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

前面三個(gè)介紹過(guò)了桥滨,剩下幾個(gè)為
MethodType samMethodType: sam(SingleAbstractMethod)就是#29 = MethodType #41 // (Ljava/lang/Object;)Ljava/lang/Object;,表示要實(shí)現(xiàn)的方法對(duì)象的類型弛车,不過(guò)它沒(méi)有泛型信息齐媒,(Ljava/lang/Object;)Ljava/lang/Object;
MethodHandle implMethod: 真正要執(zhí)行的方法的位置,這里是com.github.liuzhengyang.invokedyanmic.Runnable.lambda$run$0(Integer)Integer/invokeStatic纷跛,這里是javac生成的一個(gè)對(duì)lambda解語(yǔ)法糖之后的方法喻括,后面進(jìn)行介紹 MethodType instantiatedMethodType: 和samMethod基本一樣,不過(guò)會(huì)包含泛型信息贫奠,(Ljava/lang/Integer;)Ljava/lang/Integer;

private static java.lang.Integer lambda$run$0(java.lang.Integer);這個(gè)方法是有javac把lambda表達(dá)式desugar解語(yǔ)法糖生成的方法唬血,如果lambda表達(dá)式用到了上下文變量望蜡,則為有狀態(tài)的,這個(gè)表達(dá)式也叫做capturing-lambda拷恨,會(huì)把變量作為這個(gè)生成方法的參數(shù)傳進(jìn)來(lái)泣特,沒(méi)有狀態(tài)則為non-capturing。 另外如果使用的是java8的MethodReference挑随,例如Main::run這種語(yǔ)法則說(shuō)明有可以直接調(diào)用的方法状您,就不需要再生成一個(gè)中間方法。

繼續(xù)看5: astore_1這條指令兜挨,表示把當(dāng)前操作數(shù)棧的對(duì)象引用保存到index為1的局部變量表中膏孟,即賦值給了function變量。
說(shuō)明前面執(zhí)行完invokedynamic #2, 0 后拌汇,在操作數(shù)棧中插入了一個(gè)類型為Function的對(duì)象柒桑。
這里的過(guò)程需要繼續(xù)看一下LambdaMetafactory#metafactory的實(shí)現(xiàn)。

mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                        invokedName, samMethodType,
                                        implMethod, instantiatedMethodType,
                                        false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();

創(chuàng)建了一個(gè)InnerClassLambdaMetafactory噪舀,然后調(diào)用buildCallSite返回CallSite

看一下InnerClassLambdaMetafactory是做什么的: Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.

怎么回事魁淳!饒了一大圈還是創(chuàng)建了一個(gè)inner class!先不要慌与倡,先看完界逛,最后分析下和普通inner class的區(qū)別。

創(chuàng)建InnerClassLambdaMetafactory的過(guò)程大概是參數(shù)的一些賦值和初始化等
再看buildCallSite纺座,這個(gè)復(fù)雜一些息拜,方法描述說(shuō)明為Build the CallSite. Generate a class file which implements the functional interface, define the class, if there are no parameters create an instance of the class which the CallSite will return, otherwise, generate handles which will call the class' constructor.

創(chuàng)建一個(gè)實(shí)現(xiàn)functional interface的的class文件,define這個(gè)class净响,如果是沒(méi)有參數(shù)non-capturing類型的創(chuàng)建一個(gè)類實(shí)例少欺,CallSite可以固定返回這個(gè)實(shí)例,否則有狀態(tài)馋贤,CallSite每次都要通過(guò)構(gòu)造函數(shù)來(lái)生成新對(duì)象赞别。 這里相比普通的InnerClass,有一個(gè)內(nèi)存優(yōu)化配乓,無(wú)狀態(tài)就使用一個(gè)對(duì)象仿滔。

方法實(shí)現(xiàn)的第一步是調(diào)用spinInnerClass(),通過(guò)ASM生成一個(gè)function interface的實(shí)現(xiàn)類字節(jié)碼并且進(jìn)行類加載返回扰付。

只保留關(guān)鍵代碼
cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, lambdaClassName, null, JAVA_LANG_OBJECT, interfaces);
for (int i = 0; i < argDescs.length; i++) {
    FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, argNames[i], argDescs[i], null, null);
    fv.visitEnd();
}
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);

byte[] classBytes = cw.toByteArray();

return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);

生成方法為

  1. 聲明要實(shí)現(xiàn)的接口
  2. 創(chuàng)建保存參數(shù)用的各個(gè)字段
  3. 生成構(gòu)造函數(shù)堤撵,如果有參數(shù),則生成一個(gè)static Factory方法
  4. 實(shí)現(xiàn)function interface里的要實(shí)現(xiàn)的方法羽莺,forward到implMethodName上实昨,也就是javac生成的方法或者M(jìn)ethodReference指向的方法
  5. 生成完畢,通過(guò)ClassWrite.toByteArray拿到class字節(jié)碼數(shù)組
  6. 通過(guò)UNSAFE.defineAnonymousClass(targetClass, classBytes, null) define這個(gè)內(nèi)部類class盐固。這里的defineAnonymousClass比較特殊荒给,它創(chuàng)建出來(lái)的匿名類會(huì)掛載到targetClass這個(gè)宿主類上丈挟,然后可以用宿主類的類加載器加載這個(gè)類。但是不會(huì)但是并不會(huì)放到SystemDirectory里志电,SystemDirectory是類加載器對(duì)象+類名字到kclass地址的映射曙咽,沒(méi)有放到這個(gè)Directory里,就可以重復(fù)加載了挑辆,來(lái)方便實(shí)現(xiàn)一些動(dòng)態(tài)語(yǔ)言的功能例朱,并且能夠防止一些內(nèi)存泄露情況。

這些比較抽象鱼蝉,直觀的看一下生成的結(jié)果

// $FF: synthetic class
final class RunnableTest$$Lambda$1 implements Function {
    private RunnableTest$$Lambda$1() {
    }

    @Hidden
    public Object apply(Object var1) {
        return RunnableTest.lambda$run$0((Integer)var1);
    }
}

如果有參數(shù)的情況呢洒嗤,例如從外部類中使用了一個(gè)非靜態(tài)字段,并使用了一個(gè)外部局部變量

private int a;
void run() {
    int b = 0;
    Function<Integer, Integer> function = input -> input + 1 + a + b;
    function.apply(1);
}

對(duì)應(yīng)的結(jié)果為

final class RunnableTest$$Lambda$1 implements Function {
    private final RunnableTest arg$1;
    private final int arg$2;

    private RunnableTest$$Lambda$1(RunnableTest var1, int var2) {
        this.arg$1 = var1;
        this.arg$2 = var2;
    }

    private static Function get$Lambda(RunnableTest var0, int var1) {
        return new RunnableTest$$Lambda$1(var0, var1);
    }

    @Hidden
    public Object apply(Object var1) {
        return this.arg$1.lambda$run$0(this.arg$2, (Integer)var1);
    }
}

創(chuàng)建完inner class之后魁亦,就是生成需要的CallSite了渔隶。 如果沒(méi)有參數(shù),則生成這個(gè)inner class的一個(gè)function interface對(duì)象示例洁奈,創(chuàng)建一個(gè)固定返回這個(gè)對(duì)象的MethodHandle间唉,再包裝成ConstantCallSite返回。 如果有參數(shù)利术,則返回一個(gè)需要每次調(diào)用Factory方法產(chǎn)生function interface的對(duì)象實(shí)例的MethodHandle呈野,包裝成ConstantCallSite返回。

這樣就完成了bootstrap的過(guò)程氯哮。invokedynamic鏈接完之后际跪,后面的調(diào)用就直接調(diào)用到對(duì)應(yīng)的MethodHandle了商佛,具體是實(shí)現(xiàn)就是返回固定的內(nèi)部類對(duì)象喉钢,或每次創(chuàng)建新內(nèi)部類對(duì)象。

再次對(duì)比通過(guò)invokedynamic相對(duì)于直接匿名內(nèi)部類語(yǔ)法糖的優(yōu)勢(shì)

我們?cè)傧胍幌铝寄罚琂ava8實(shí)現(xiàn)這一套騷操作的原因是什么肠虽。 既然lambda表達(dá)式又不需要什么動(dòng)態(tài)分派(調(diào)動(dòng)哪個(gè)方法是明確的), 為什么要用invokedynamic呢? JVM虛擬機(jī)的一個(gè)基本保證就是低版本的class文件也是能夠在高版本的JVM上運(yùn)行的玛追,并且JVM虛擬機(jī)通過(guò)版本升級(jí)税课,是在不斷優(yōu)化和提升性能的。

直接轉(zhuǎn)換成內(nèi)部類實(shí)現(xiàn)痊剖,固然簡(jiǎn)單韩玩,但編譯后的二進(jìn)制字節(jié)碼(包括第三方j(luò)ar包等)內(nèi)容就固定了,實(shí)現(xiàn)固定為創(chuàng)建內(nèi)部類對(duì)象+invoke{virtual, static, special, interface}調(diào)用陆馁。
未來(lái)提升性能只能靠提升創(chuàng)建類對(duì)象找颓、invoke指令調(diào)用這幾個(gè)地方的優(yōu)化。換個(gè)熟悉點(diǎn)的說(shuō)法就是這里寫死了叮贩。 如果通過(guò)invokedynamic呢击狮,javac編譯后把足夠的信息保留了下來(lái)佛析,在JVM執(zhí)行時(shí)能夠動(dòng)態(tài)決定如何實(shí)現(xiàn)lambda,也就能不斷優(yōu)化lambda表達(dá)式的實(shí)現(xiàn)彪蓬,并保持兼容性寸莫,給未來(lái)留下了更多可能。

總結(jié)

本文是我學(xué)習(xí)lambda的一些總結(jié)档冬,介紹了lambda表達(dá)式出現(xiàn)的原因膘茎、實(shí)現(xiàn)方法以及不同實(shí)現(xiàn)思路上的對(duì)比。 對(duì)lambda知識(shí)也只是略看了一些代碼酷誓、資料辽狈,如有錯(cuò)誤或不明確的地方還請(qǐng)大家無(wú)情指出。

本文使用 文章同步助手 同步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呛牲,一起剝皮案震驚了整個(gè)濱河市刮萌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娘扩,老刑警劉巖着茸,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異琐旁,居然都是意外死亡涮阔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門灰殴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敬特,“玉大人,你說(shuō)我怎么就攤上這事牺陶∥袄” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵掰伸,是天一觀的道長(zhǎng)皱炉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狮鸭,這世上最難降的妖魔是什么合搅? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮歧蕉,結(jié)果婚禮上灾部,老公的妹妹穿的比我還像新娘。我一直安慰自己惯退,他們只是感情好赌髓,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般春弥。 火紅的嫁衣襯著肌膚如雪呛哟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天匿沛,我揣著相機(jī)與錄音扫责,去河邊找鬼。 笑死逃呼,一個(gè)胖子當(dāng)著我的面吹牛鳖孤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抡笼,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼苏揣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了推姻?” 一聲冷哼從身側(cè)響起平匈,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藏古,沒(méi)想到半個(gè)月后增炭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧晕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年隙姿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厂捞。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡输玷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出靡馁,到底是詐尸還是另有隱情欲鹏,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布奈嘿,位于F島的核電站貌虾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏裙犹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一衔憨、第九天 我趴在偏房一處隱蔽的房頂上張望叶圃。 院中可真熱鬧,春花似錦践图、人聲如沸掺冠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)德崭。三九已至斥黑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間眉厨,已是汗流浹背锌奴。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留憾股,地道東北人鹿蜀。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像服球,于是被迫代替她去往敵國(guó)和親茴恰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容