Lambda表達式介紹和底層實現分析

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你的需求需要匿名類來實現,例如是一個只有一個方法的接口,那麼匿名類的語法可能看起來比較笨拙和不清晰,儘管匿名類比命名類更簡潔,但對於只有一個方法的類來說,即使是匿名類也顯得有些麻煩。還有在一些情況下,需要將功能作爲參數傳遞給另一個方法,例如當有人單擊頁面上按鈕時應該採取什麼操作,javascript可以通過閉包實現。在java語言中,lambda表達式能夠將功能視爲方法參數,或將代碼視爲數據,而且lambda表達式可以更緊湊地表達單方法類的實例,在Swing編程和集合(Collections)編程中優勢很明顯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"lambda表達式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lambda表達式,也被稱爲閉包,它是推動 Java 8 發佈的最重要新特性。lambda允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lambda 表達式的語法格式如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"(parameters) -> expression或 (parameters) ->{ statements; }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"以下是lambda表達式的重要特徵:"}]},{"type":"image","attrs":{"src":"https:\/\/static001.infoq.cn\/resource\/image\/06\/74\/06954edcbd80b0623048148485e1c574.png","alt":null,"title":"","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如下面是一些lambda表達式"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"(int x, int y) -> x + y\n() -> 42\n(String s) -> { System.out.println(s); }\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"函數式接口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"只有一個抽象方法的接口,稱爲函數式接口。例如下面的接口,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public interface MyFunctionInterface {\n public T getValue(T t);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"測試類如下"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public class MyFunctionInterfaceTest {\n public static void main(String[] args) {\n testMethod(\" aaaa \", s -> s.trim());\n testMethod(\" aaaa \", s -> s.trim().toUpperCase());\n }\n\n public static void testMethod(String str, MyFunctionInterface functionInterface) {\n System.out.println(functionInterface.getValue(str));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"輸出結果如下:"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"aaaa\nAAAA"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"修改一下上面的接口,增加一個方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public interface MyFunctionInterface {\n public T getValue(T t);\n public T returnValue(T t);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增加一個方法之後,上面就不是函數式接口了,可以看到lambda表達式就會報錯。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/d3\/d3c9c696bddbe22c481e1330626317fc.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然不能在函數式接口中定義多個方法,但可以定義默認方法、靜態方法以及java.lang.Object裏的public方法。如下面的代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@FunctionalInterface\npublic interface MyFunctionInterface {\n\n public T getValue(T t);\n\n default void defaultMethod() {\n System.out.println(\"this is default method\");\n }\n\n static void staticMethod() {\n System.out.println(\"this is static method\");\n }\n\n\n public boolean equals(Object obj);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以在接口上使用@FunctionalInterface註解,如果使用Intellij IDEA可以在編碼的時候就看見報錯了,這樣做可以檢查它是否是一個函數式接口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/02\/02adf440afdfacad2487466d1077052e.jpeg","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過反編譯,可以看到函數式接口其實就是一個普通的java接口類,如下圖"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/23\/23e4b290bef9ff1e64d18ad7152c7a81.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數式接口可以作爲方法參數傳遞lambda表達式,但是爲了將Lambda表達式作爲參數傳遞,接收Lambda表達式的參數類型必須是與該Lambda表達式兼容的函數式接口的類型。但是我們沒必要爲每一個lambda表達式創建接口,在jdk的java.util.function包下面已經爲我們創建了常用的函數式接口,其中比較核心的是消費型接口(Consumer),供給型接口(Supplier),斷言型接口(Predicate),函數型接口(Function)四個接口,能夠滿足大部分應用場景。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"lambda表達式原理分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續使用上面的測試代碼,可以在IDEA中使用jclasslib Bytecode viewer 插件查看MyFunctionInterface.class源文件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"安裝完jclasslib Bytecode viewer,會在view菜單中出現如下兩個選項"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b9\/b9b3dbe6c16e2a5889d9fd83fcf777b9.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇需要反編譯的class文件,點擊Show Bytecode with Jclasslib選項會出現如下界面"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a2\/a2a94a758879d7f3f24413d0013f9609.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到裏面編譯器多生成了lambda$main$0和lambda$main$1兩個私有靜態方法,屬性當中多了InnerClasses。我們可以通過Show Bytecode查看一下測試類字節碼更詳細的反編譯結果,找到這兩個靜態方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ access flags 0x100A\nprivate static synthetic lambda$main$1(Ljava\/lang\/String;)Ljava\/lang\/String;\n L0\n LINENUMBER 6 L0\n ALOAD 0\n INVOKEVIRTUAL java\/lang\/String.trim ()Ljava\/lang\/String;\n INVOKEVIRTUAL java\/lang\/String.toUpperCase ()Ljava\/lang\/String;\n ARETURN\n L1\n LOCALVARIABLE s Ljava\/lang\/String; L0 L1 0\n MAXSTACK = 1\n MAXLOCALS = 1\n\n\/\/ access flags 0x100A\nprivate static synthetic lambda$main$0(Ljava\/lang\/String;)Ljava\/lang\/String;\n L0\n LINENUMBER 5 L0\n ALOAD 0\n INVOKEVIRTUAL java\/lang\/String.trim ()Ljava\/lang\/String;\n ARETURN\n L1\n LOCALVARIABLE s Ljava\/lang\/String; L0 L1 0\n MAXSTACK = 1\n MAXLOCALS = 1\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到兩個私有的靜態方法乾的就是Lambda表達式裏面的內容,那麼又是如何調用的生成的私有靜態方法呢?如下圖,通過分析main方法的L0,首先通過INVOKEDYNAMIC 指令調用是MyFunctionInterface的getValue方法的引用,以及後面的BootstrapMethods #0。使用jclasslib Bytecode viewer查看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/6d\/6d5aed16e1fcf0c15e1b2ae2fe2e6c84.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點擊#3,進入下面界面"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/24\/246262711301e4aae320015264c60f74.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點擊BootstrapMethods #0,進入如下界面"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/20\/20fc864945ee6ec8c14bada77f68f177.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"點擊cp_info #44,進入如下界面"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/70\/70311f0b5854c7c6ad5d51f334421467.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續點擊相應的方法描述符,我們可以看到最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/57\/5786a599c21d98ad27bc53c621255802.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cp_info #74 內容如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"(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;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到INVOKEDYNAMIC 後面的一系列指令,最後使用INVOKESTATIC調用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"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;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"查看LambdaMetafactory.metafactory的方法,裏面通過InnerClassLambdaMetafactory生成了CallSite的子類ConstantCallSite,當通過指令調用CallSite會返回函數式接口的實例,而生成接口實例的方式是通過內部類的方式,由於方法比較深,就不繼續貼代碼了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public static CallSite metafactory(MethodHandles.Lookup caller,\n String invokedName,\n MethodType invokedType,\n MethodType samMethodType,\n MethodHandle implMethod,\n MethodType instantiatedMethodType)\n throws LambdaConversionException {\n AbstractValidatingLambdaMetafactory mf;\n mf = new InnerClassLambdaMetafactory(caller, invokedType,\n invokedName, samMethodType,\n implMethod, instantiatedMethodType,\n false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);\n mf.validateMetafactoryArgs();\n return mf.buildCallSite();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/49\/498219745674441489b8f1d128e01de8.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CallSite持有com\/para\/lambda\/MyFunctionInterfaceTest.lambda$main$0方法的句柄,這個句柄會調用該方法。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/d0\/d0d2c57ecabdaadb51362086ed02116f.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以使用lambda表達式的地方,會在類編譯的時候在本類中生成對應的私有靜態方法和一個INNERCLASS的訪問標識(具體是什麼東西沒找到資料,註釋顯示是一個訪問標識),該訪問標識會調用引導類加載器動態生成內部類,該內部類實現了函數式接口,在實現接口的方法中,會調用編譯器生成靜態方法,在使用lambda表達式的地方,通過傳遞內部類實例,來調用函數式接口方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文從lambda表達式、函數式接口的介紹和對lambda表達式底層原理的分析來認識java中的函數式編程。函數式接口本身就是一個普通的接口,而lambda表達式本質上和匿名內部類是一樣的,只不過條件更加苛刻。使用lamda表達式可以以一種更優雅的方式來編程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:360技術(ID:qihoo_tech)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/CV1iuoqyK76z4u6N-h0geQ","title":"xxx","type":null},"content":[{"type":"text","text":"Lambda表達式介紹和底層實現分析"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章