摘要:本文已收錄修煉內功躍遷之路初次接觸的時候感覺表達式很神奇表達式帶來的編程新思路,但又總感覺它就是匿名類或者內部類的語法糖而已,只是語法上更為簡潔罷了,如同以下的代碼匿名類內部類編譯后會產生三個文件雖然從使用效果來看,與匿名類或者內部類有相
本文已收錄【修煉內功】躍遷之路
初次接觸Java8的時候感覺Lambda表達式很神奇(Lambda表達式帶來的編程新思路),但又總感覺它就是匿名類或者內部類的語法糖而已,只是語法上更為簡潔罷了,如同以下的代碼
public class Lambda { private static void hello(String name, Consumerprinter) { printer.accept(name); } public static void main(String[] args) { hello("lambda", (name) -> System.out.println("Hello " + name)); hello("匿名類", new Consumer () { @Override public void accept(String name) { System.out.println("Hello " + name); } }); hello("內部類", new SupplierImpl()); } static class SupplierImpl implements Consumer { @Override public void accept(String name) { System.out.println("Hello " + name); } } }
編譯后會產生三個文件
雖然從使用效果來看,Lambda與匿名類或者內部類有相似之處(當然也有很大不同,如this指針等 Lambda表達式里的"陷阱"),但從編譯結果來看,并不能簡單地將Lambda與匿名類/內部類劃等號
簡單查看Lambda字節碼javap -p Lambda
Java編譯器自動幫我們生成了方法lambda$main$0,我們有理由相信,Lambda表達式內的邏輯就封裝在此函數內
生成的方法lambda$main$0又是如何被調用的呢?
Lambda的調用使用了invokedynamic指令,虛擬機視角的方法調用一文中已經詳細介紹了invokedynamic,但這里還是看不出invokedynamic指令與lambda$main$0方法之間到底是如何關聯起來的,invokedynamic指令的啟動函數(BootstrapMethod)與調用點(CallSite)又在哪里?
其實仔細查看字節碼的話可以發下,編譯器還會額外生成一個內部類
仔細查看內部類的邏輯,是不是像極了虛擬機視角的方法調用一文中所提invokedynamic的運行過程
在第一次執行invokedynamic時,JVM虛擬機會調用該指令所對應的啟動方法(BootstrapMethod)來生成調用點
啟動方法(BootstrapMethod)由方法句柄來指定(MH_BootstrapMethod)
啟動方法接受三個固定的參數,分別為 Lookup實例、指代目標方法名的字符串及該調用點能夠鏈接的方法句柄類型
將調用點綁定至該invokedynamic指令中,之后的運行中虛擬機會直接調用綁定的調用點所鏈接的方法句柄
為了驗證此想法,可以執行java -Djdk.internal.lambda.dumpProxyClasses Lambda用來導出內部類
跟蹤內部類的運行可以發現,在執行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, Consumerprinter) { 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文件
對于每一個lambda表達式,都會生成一個靜態的私有方法
再查看內部類
只會生成一個內部類,但存在三個方法,每個方法對應一個lambda私有方法,用以生成對應的調用點綁定到相應的invokedynamic指令上
結合以上我們可以總結出:
lambda表達式會被編譯為invokedynamic指令
每一個lambda表達式的實現邏輯均會被封裝為一個靜態私有方法
只要存在lambda表達式調用,便會生成一個內部類
內部類中每一個方法(啟動方法 BoostrapMethod)對應一個lambda表達式所生成的靜態私有方法,內部類中的方法用以生成對應的調用點綁定到相應的invokedynamic指令上
這也解釋了為什么lambda中的this指針指向的是周圍的類 (定義該Lambda表達式時所處的類) (Lambda表達式里的"陷阱")
所以,lambda表達式確實是語法糖,但并不是匿名類/內部類的語法糖
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74996.html
摘要:本文已收錄修煉內功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機執行的對于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機在運行時又是如何確定具體方法的方法調用不等于方法執行,一切方法調用在文件中都只是常量池中的符號引 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...
摘要:表達式體現了函數式編程的思想,即一個函數亦可以作為另一個函數參數和返回值,使用了函數作參數返回值的函數被稱為高階函數。對流對象進行及早求值,返回值不在是一個對象。 Java8主要的改變是為集合框架增加了流的概念,提高了集合的抽象層次。相比于舊有框架直接操作數據的內部處理方式,流+高階函數的外部處理方式對數據封裝更好。同時流的概念使得對并發編程支持更強。 在語法上Java8提供了Lamb...
摘要:初體驗下面進入本文的正題表達式。接下來展示表達式和其好基友的配合。吐槽一下方法引用表面上看起來方法引用和構造器引用進一步簡化了表達式的書寫,但是個人覺得這方面沒有的下劃線語法更加通用。 感謝同事【天錦】的投稿。投稿請聯系 tengfei@ifeve.com 本文主要記錄自己學習Java8的歷程,方便大家一起探討和自己的備忘。因為本人也是剛剛開始學習Java8,所以文中肯定有錯誤和理解偏...
摘要:的本質需求按照產品的重量進行升序排序此處使用匿名內部類的設計,但摻雜了較多的語法噪聲,引入了不必要的復雜度。使用表達式,可以進一步消除語法噪聲,簡化設計。方法引用其本質是具有單一方法調用的表達式的語法糖表示。 Lambda的本質 需求1. 按照產品的重量進行升序排序 此處使用「匿名內部類」的設計,但摻雜了較多的語法噪聲,引入了不必要的復雜度。 Collections.sort(repo...
摘要:引入了與此前完全不同的函數式編程方法,通過表達式和來為下的函數式編程提供動力。命令式編程語言把對象變量和流轉當作一等公民,而函數式編程在此基礎上加入了策略變量這一新的一等公民。 Java8引入了與此前完全不同的函數式編程方法,通過Lambda表達式和StreamAPI來為Java下的函數式編程提供動力。本文是Java8新特性的第一篇,旨在闡釋函數式編程的本義,更在展示Java是如何通...
閱讀 3267·2021-11-24 09:38
閱讀 2148·2021-11-23 09:51
閱讀 1738·2021-10-13 09:39
閱讀 2610·2021-09-23 11:53
閱讀 1394·2021-09-02 15:40
閱讀 3648·2019-08-30 15:54
閱讀 1121·2019-08-30 13:04
閱讀 2552·2019-08-30 11:01