本篇文章給大家分享的是有關(guān)Java中Lambda表達式的實現(xiàn)原理是什么,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
衢州網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián),衢州網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為衢州上1000+提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請找那個售后服務(wù)好的衢州做網(wǎng)站的公司定做!
ListidList = Arrays.asList(1L, 2L, 3L); List personList = new ArrayList<>(); for (long id : idList) { personList.add(getById(id)); }
代碼重復(fù)多了之后,大家就會對這種常見代碼進行抽象,形成一些類庫便于復(fù)用。需求可以抽象成:對列表中的每個元素調(diào)用一個轉(zhuǎn)換函數(shù)Function轉(zhuǎn)換并輸出結(jié)果列表。
interface Function {R fun(T input); } List map(List inputList, Function function) { List mappedList = new ArrayList<>(); for (T t : inputList) { mappedList.add(function.fun(t)); } return mappedList; }
有了這個抽象組件方法,最開始的代碼便可以”簡化”成:
ListidList = Arrays.asList(1L, 2L, 3L); List personList = map(idList, new Function () { @Override public Person fun(Long input) { return getById(input); } });
因為Java語言中函數(shù)并不能作為參數(shù)傳遞到方法中,函數(shù)只能寄存在一個類中表示。為了能夠把函數(shù)作為參數(shù)傳遞到方法中,我們被迫使用了匿名內(nèi)部類實現(xiàn),需要加相當(dāng)多的冗余代碼。
在一些支持函數(shù)式編程的語言(Functional Programming Language)中(例如Python, Scala, Kotlin等),函數(shù)是一等公民,函數(shù)可以成為參數(shù)傳遞以及作為返回值返回。例如在Kotlin中,上述的代碼可以縮減到很短,代碼只包含關(guān)鍵內(nèi)容,沒有冗余信息。
val personList = idList.map { id -> getById(id) }
這樣的編寫效率差距也導(dǎo)致了一部分Java用戶流失到其他語言,不過最終終于在JDK8也提供了Lambda表達式能力,來支持這種函數(shù)傳遞。
ListpersonList = map(idList, input -> getById(input));
如果要在Java語言中實現(xiàn)lambda表達式,初步觀察,通過javac把這種箭頭語法還原成匿名內(nèi)部類,就可以輕松實現(xiàn),因為它們功能基本是等價的(IDEA中經(jīng)常有提示)。
1.每個匿名內(nèi)部類都會在編譯時創(chuàng)建一個對應(yīng)的class,并且是有文件的,因此在運行時不可避免的會有加載、驗證、準(zhǔn)備、解析、初始化的類加載過程。
2.每次調(diào)用都會創(chuàng)建一個這個匿名內(nèi)部類class的實例對象,無論是有狀態(tài)的(capturing,從上下文中捕獲一些變量)還是無狀態(tài)(non-capturing)的內(nèi)部類。
如果有一種函數(shù)引用、指針就好了,但JVM中并沒有函數(shù)類型表示。
Java中有表示函數(shù)引用的對象嗎,反射中有個Method對象,但它的問題是性能問題,每次執(zhí)行都會進行安全檢查,且參數(shù)都是Object類型,需要boxing等等。還有其他表示函數(shù)引用的方法嗎?MethodHandle,在JDK7中與invokedynamic指令等一起提供的新特性。
直接使用MethodHandle來實現(xiàn),由于沒有簽名信息,會遇不能重載的問題。并且MethodHandle的invoke方法性能不一定能保證比字節(jié)碼調(diào)用好。
JVM上動態(tài)語言(JRuby, Scala等),實現(xiàn)dynamic typing動態(tài)類型,是比較麻煩的。這里簡單解釋一下什么是dynamic typing,與其相對的是static typing靜態(tài)類型。
static typing: 所有變量的類型在編譯時都是確定的,并且會進行類型檢查。
dynamic typing: 變量的類型在編譯時不能確定,只能在運行時才能確定、檢查。
例如,如下動態(tài)語言的例子,a和b的類型都是未知的,因此a.append(b)這個方法是什么也是未知的。
def add(val a, val b) a.append(b)
而在Java中a和b的類型在編譯時就能確定。
SimpleString add(SimpleString a, SimpleString b) { return a.append(b); }
編譯后的字節(jié)碼如下,通過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)用實例非接口方法的public方法
invokespecial - 其他的方法調(diào)用,private,constructor, super
這幾種方法調(diào)用指令,在編譯的時候就已經(jīng)明確指定了要調(diào)用什么樣的方法,且均需要接收一個明確的常量池中的方法的符號引用,并進行類型檢查,是不能隨便傳一個不滿足類型要求的對象來調(diào)用的,即使傳過來的類型中也恰好有一樣的方法簽名也不行。
這個限制讓JVM上的動態(tài)語言實現(xiàn)者感到很艱難,只能暫時通過性能較差的反射等方式實現(xiàn)動態(tài)類型。
這說明在字節(jié)碼層面無法支持動態(tài)分派,該怎么辦呢,又用到了大家熟悉的”All problems in computer science can be solved by another level of indirection”了。
現(xiàn)動態(tài)分派,既然不能在編譯時決定,那么我們把這個決策推遲到運行時再決定,由用戶的自定義代碼告訴給JVM要執(zhí)行什么方法。
在jdk7,Java提供了invokedynamic指令來解決這個問題,同時搭配的還有java.lang.invoke包。這個指令大部分用戶不太熟悉,因為不像invokestatic等指令,它在Java語言中并沒有和它相關(guān)的直接概念。
invokedynamic指令: 運行時JVM第一次到這里的時候會進行l(wèi)inkage,會調(diào)用用戶指定的bootstrap method來決定要執(zhí)行什么方法,之后便不需要這個解析步驟。這個invokedynamic指令出現(xiàn)的地方也叫做dynamic call site。
Bootstrap Method: 用戶可以自己編寫的方法,實現(xiàn)自己的邏輯最終返回一個CallSite對象。
CallSite: 負責(zé)通過getTarget()方法返回MethodHandle
MethodHandle: MethodHandle表示的是要執(zhí)行的方法的指針
invokedynamic在最開始時處于未鏈接(unlinked)狀態(tài),這時這個指令并不知道要調(diào)用的目標(biāo)方法是什么。
當(dāng)JVM要第一次執(zhí)行某個地方的invokedynamic指令的時候,invokedynamic必須先進行鏈接(linkage)。
鏈接過程通過調(diào)用一個bootstrap method,傳入當(dāng)前的調(diào)用相關(guān)信息,bootstrap method會返回一個CallSite,這個CallSite中包含了MethodHandle的引用,也就是CallSite的target。
invokedynamic指令便鏈接到這個CallSite上,并把所有的調(diào)用delegate到它當(dāng)前的targetMethodHandle上。根據(jù)target是否需要變換,CallSite可以分為MutableCallSite、ConstantCallSite和VolatileCallSite等,可以通過切換target MethodHandle實現(xiàn)動態(tài)修改要調(diào)用的方法。
下面直接看一下目前java實現(xiàn)lambda的方式 以下面的代碼為例
public class RunnableTest { void run() { Functionfunction = input -> input + 1; function.apply(1); } }
編譯后通過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; 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;
對應(yīng)Function
0: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 5: astore_1
JVM第一次解析時,調(diào)用用戶定義的bootstrap method
bootstrap method會返回一個CallSite
CallSite中能夠得到MethodHandle,表示方法指針
JVM之后調(diào)用這里就不再需要重新解析,直接綁定到這個CallSite上,調(diào)用對應(yīng)的target MethodHandle,并能夠進行inline等調(diào)用優(yōu)化
第一行invokedynamic后面有兩個參數(shù),第二個0沒有意義固定為0 第一個參數(shù)是#2,指向的是常量池中類型為CONSTANT_InvokeDynamic_info的常量。
#2 = InvokeDynamic #0:#32 // #0:apply:()Ljava/util/function/Function;
這個常量對應(yīng)的#0:#32中第二個#32表示的是這個invokedynamic指令對應(yīng)的動態(tài)方法的名字和方法簽名(方法類型)
#32 = NameAndType #43:#44 // apply:()Ljava/util/function/Function;
第一個#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屬性對應(yīng)JVM虛擬機規(guī)范里的說明。
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 這個BootstrapMethod屬性可以告訴invokedynamic指令需要的boostrap method的引用以及參數(shù)的數(shù)量和類型。 #28對應(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;
按照JVM規(guī)范,BootstrapMethod接收3個標(biāo)準(zhǔn)參數(shù)和一些自定義參數(shù),標(biāo)準(zhǔn)參數(shù)如下MethodHandles.$Lookup類型的caller參數(shù),這個對象能夠通過類似反射的方式拿到在執(zhí)行invokedynamic指令這個環(huán)境下能夠調(diào)動到的方法,比如其他類的private方法是調(diào)用不到的。
這個參數(shù)由JVM來入棧String類型的invokedName參數(shù),表示invokedynamic要實現(xiàn)的方法的名字,在這里是apply,是lambda表達式實現(xiàn)的方法名,這個參數(shù)由JVM來入棧MethodType類型的invokedType參數(shù),表示invokedynamic要實現(xiàn)的方法的類型,在這里是()Function,這個參數(shù)由JVM來入棧
#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;
通過java.lang.invoke.LambdaMetafactory#metafactory的代碼說明下
public static CallSite metafactory(MethodHandles.Lookup caller, String invokedName, MethodType invokedType, MethodType samMethodType, MethodHandle implMethod, MethodType instantiatedMethodType)
前面三個介紹過了,剩下幾個為
MethodType samMethodType: sam(SingleAbstractMethod)就是#29 = MethodType #41 // (Ljava/lang/Object;)Ljava/lang/Object;,表示要實現(xiàn)的方法對象的類型,不過它沒有泛型信息,(Ljava/lang/Object;)Ljava/lang/Object;
MethodHandle implMethod: 真正要執(zhí)行的方法的位置,這里是com.github.liuzhengyang.invokedyanmic.Runnable.lambda$run$0(Integer)Integer/invokeStatic,這里是javac生成的一個對lambda解語法糖之后的方法,后面進行介紹
MethodType instantiatedMethodType: 和samMethod基本一樣,不過會包含泛型信息,(Ljava/lang/Integer;)Ljava/lang/Integer;
private static java.lang.Integer lambda$run$0(java.lang.Integer);這個方法是有javac把lambda表達式desugar解語法糖生成的方法,如果lambda表達式用到了上下文變量,則為有狀態(tài)的,這個表達式也叫做capturing-lambda,會把變量作為這個生成方法的參數(shù)傳進來,沒有狀態(tài)則為non-capturing。
另外如果使用的是java8的MethodReference,例如Main::run這種語法則說明有可以直接調(diào)用的方法,就不需要再生成一個中間方法。
繼續(xù)看5: astore_1這條指令,表示把當(dāng)前操作數(shù)棧的對象引用保存到index為1的局部變量表中,即賦值給了function變量。
說明前面執(zhí)行完invokedynamic #2, 0后,在操作數(shù)棧中插入了一個類型為Function的對象。
這里的過程需要繼續(xù)看一下LambdaMetafactory#metafactory的實現(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)建了一個InnerClassLambdaMetafactory,然后調(diào)用buildCallSite返回CallSite
看一下InnerClassLambdaMetafactory是做什么的: Lambda metafactory implementation which dynamically creates an inner-class-like class per lambda callsite.
怎么回事!饒了一大圈還是創(chuàng)建了一個inner class!先不要慌,先看完,最后分析下和普通inner class的區(qū)別。
創(chuàng)建InnerClassLambdaMetafactory的過程大概是參數(shù)的一些賦值和初始化等,再看buildCallSite,這個復(fù)雜一些,方法描述說明為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)建一個實現(xiàn)functional interface的的class文件,define這個class,如果是沒有參數(shù)non-capturing類型的創(chuàng)建一個類實例,CallSite可以固定返回這個實例,否則有狀態(tài),CallSite每次都要通過構(gòu)造函數(shù)來生成新對象。 這里相比普通的InnerClass,有一個內(nèi)存優(yōu)化,無狀態(tài)就使用一個對象。
方法實現(xiàn)的第一步是調(diào)用spinInnerClass(),通過ASM生成一個function interface的實現(xiàn)類字節(jié)碼并且進行類加載返回。
只保留關(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);
生成方法為
聲明要實現(xiàn)的接口
創(chuàng)建保存參數(shù)用的各個字段
生成構(gòu)造函數(shù),如果有參數(shù),則生成一個static Factory方法
實現(xiàn)function interface里的要實現(xiàn)的方法,forward到implMethodName上,也就是javac生成的方法或者MethodReference指向的方法
生成完畢,通過ClassWrite.toByteArray拿到class字節(jié)碼數(shù)組
通過UNSAFE.defineAnonymousClass(targetClass, classBytes, null) define這個內(nèi)部類class。這里的defineAnonymousClass比較特殊,它創(chuàng)建出來的匿名類會掛載到targetClass這個宿主類上,然后可以用宿主類的類加載器加載這個類。但是不會但是并不會放到SystemDirectory里,SystemDirectory是類加載器對象+類名字到kclass地址的映射,沒有放到這個Directory里,就可以重復(fù)加載了,來方便實現(xiàn)一些動態(tài)語言的功能,并且能夠防止一些內(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ù)的情況呢,例如從外部類中使用了一個非靜態(tài)字段,并使用了一個外部局部變量 private int a; void run() { int b = 0; Functionfunction = input -> input + 1 + a + b; function.apply(1); } 對應(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了。
如果沒有參數(shù),則生成這個inner class的一個function interface對象示例,創(chuàng)建一個固定返回這個對象的MethodHandle,再包裝成ConstantCallSite返回。
如果有參數(shù),則返回一個需要每次調(diào)用Factory方法產(chǎn)生function interface的對象實例的MethodHandle,包裝成ConstantCallSite返回。
這樣就完成了bootstrap的過程。invokedynamic鏈接完之后,后面的調(diào)用就直接調(diào)用到對應(yīng)的MethodHandle了,具體是實現(xiàn)就是返回固定的內(nèi)部類對象,或每次創(chuàng)建新內(nèi)部類對象。
既然lambda表達式又不需要什么動態(tài)分派(調(diào)動哪個方法是明確的), 為什么要用invokedynamic呢?
JVM虛擬機的一個基本保證就是低版本的class文件也是能夠在高版本的JVM上運行的,并且JVM虛擬機通過版本升級,是在不斷優(yōu)化和提升性能的。
直接轉(zhuǎn)換成內(nèi)部類實現(xiàn),固然簡單,但編譯后的二進制字節(jié)碼(包括第三方j(luò)ar包等)內(nèi)容就固定了,實現(xiàn)固定為創(chuàng)建內(nèi)部類對象+invoke{virtual, static, special, interface}調(diào)用。
以上就是Java中Lambda表達式的實現(xiàn)原理是什么,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降?。希望你能通過這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。