lambda表達(dá)式
wiki
- 通過字節(jié)碼分析JDK8中Lambda表達(dá)式編譯及執(zhí)行機(jī)制
- Java 8 Lambda 揭秘
- Java8學(xué)習(xí)筆記(4) -- Lambda表達(dá)式實(shí)現(xiàn)方式
- 關(guān)于OpenJDK對(duì)Java 8 lambda表達(dá)式的運(yùn)行時(shí)實(shí)現(xiàn)的查看方式
- 理解 invokedynamic
- Invokedynamic:Java的秘密武器
學(xué)習(xí)目標(biāo)
- 代碼層面理解怎么使用lambda表達(dá)式贴铜;
- 編譯層面理解lambda的機(jī)制屑柔;
- 理解invokedynamic指令的機(jī)制岁疼;
- bootstrap方法的參數(shù)具體是什么,以及怎么調(diào)試得到參數(shù)踢代;
- 疑問
- metafactory方法的參數(shù)具體表示什么?
- 字節(jié)碼中關(guān)于boostraps方法的部分怎么理解?
How:實(shí)現(xiàn)原理
使用代碼
package CompilerTestPackage;
import java.util.function.Consumer;
public class LambdaTest {
public static void main(String[] args)
{
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.getName());
greeter.accept(new Person("Lambda"));
}
}
class Person{
public String name;
public Person(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
通過cfr反編譯代碼
-
命令
java -jar .\cfr_0_132.jar .\LambdaTest.class --decodelambdas false > D:\LambdTestCfr.txt
package CompilerTestPackage;
import CompilerTestPackage.Person;
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.function.Consumer;
public class LambdaTest {
public static void main(String[] args) {
Consumer<Person> greeter = (Consumer<Person>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(CompilerTestPackage.Person ), (LCompilerTestPackage/Person;)V)();
greeter.accept(new Person("Lambda"));
}
private static /* synthetic */ void lambda$main$0(Person p) {
System.out.println("Hello, " + p.getName());
}
}
- Lambda表達(dá)式生成了一個(gè)lambda0()方法岳守;
- 調(diào)用LambdaMetafactory.metafactory()方法
字節(jié)碼層面
Classfile /D:/test/LambdaTest.class
Last modified 2018-8-5; size 1703 bytes
MD5 checksum f7703a9885aa7896abfb5a773b708be2
Compiled from "LambdaTest.java"
public class CompilerTestPackage.LambdaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #16.#38 // java/lang/Object."<init>":()V
#2 = InvokeDynamic #0:#44 // #0:accept:()Ljava/util/function/Consumer;
#3 = Class #45 // CompilerTestPackage/Person
#4 = String #46 // Lambda
#5 = Methodref #3.#47 // CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
#6 = InterfaceMethodref #48.#49 // java/util/function/Consumer.accept:(Ljava/lang/Object;)V
#7 = Fieldref #50.#51 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #52 // java/lang/StringBuilder
#9 = Methodref #8.#38 // java/lang/StringBuilder."<init>":()V
#10 = String #53 // Hello,
#11 = Methodref #8.#54 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#12 = Methodref #3.#55 // CompilerTestPackage/Person.getName:()Ljava/lang/String;
#13 = Methodref #8.#56 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#14 = Methodref #57.#58 // java/io/PrintStream.println:(Ljava/lang/String;)V
#15 = Class #59 // CompilerTestPackage/LambdaTest
#16 = Class #60 // java/lang/Object
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 LCompilerTestPackage/LambdaTest;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 greeter
#29 = Utf8 Ljava/util/function/Consumer;
#30 = Utf8 LocalVariableTypeTable
#31 = Utf8 Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
#32 = Utf8 lambda$main$0
#33 = Utf8 (LCompilerTestPackage/Person;)V
#34 = Utf8 p
#35 = Utf8 LCompilerTestPackage/Person;
#36 = Utf8 SourceFile
#37 = Utf8 LambdaTest.java
#38 = NameAndType #17:#18 // "<init>":()V
#39 = Utf8 BootstrapMethods
#40 = MethodHandle #6:#61 // 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;
#41 = MethodType #62 // (Ljava/lang/Object;)V
#42 = MethodHandle #6:#63 // invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#43 = MethodType #33 // (LCompilerTestPackage/Person;)V
#44 = NameAndType #64:#65 // accept:()Ljava/util/function/Consumer;
#45 = Utf8 CompilerTestPackage/Person
#46 = Utf8 Lambda
#47 = NameAndType #17:#66 // "<init>":(Ljava/lang/String;)V
#48 = Class #67 // java/util/function/Consumer
#49 = NameAndType #64:#62 // accept:(Ljava/lang/Object;)V
#50 = Class #68 // java/lang/System
#51 = NameAndType #69:#70 // out:Ljava/io/PrintStream;
#52 = Utf8 java/lang/StringBuilder
#53 = Utf8 Hello,
#54 = NameAndType #71:#72 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#55 = NameAndType #73:#74 // getName:()Ljava/lang/String;
#56 = NameAndType #75:#74 // toString:()Ljava/lang/String;
#57 = Class #76 // java/io/PrintStream
#58 = NameAndType #77:#66 // println:(Ljava/lang/String;)V
#59 = Utf8 CompilerTestPackage/LambdaTest
#60 = Utf8 java/lang/Object
#61 = Methodref #78.#79 // 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;
#62 = Utf8 (Ljava/lang/Object;)V
#63 = Methodref #15.#80 // CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#64 = Utf8 accept
#65 = Utf8 ()Ljava/util/function/Consumer;
#66 = Utf8 (Ljava/lang/String;)V
#67 = Utf8 java/util/function/Consumer
#68 = Utf8 java/lang/System
#69 = Utf8 out
#70 = Utf8 Ljava/io/PrintStream;
#71 = Utf8 append
#72 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#73 = Utf8 getName
#74 = Utf8 ()Ljava/lang/String;
#75 = Utf8 toString
#76 = Utf8 java/io/PrintStream
#77 = Utf8 println
#78 = Class #81 // java/lang/invoke/LambdaMetafactory
#79 = NameAndType #82:#86 // 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;
#80 = NameAndType #32:#33 // lambda$main$0:(LCompilerTestPackage/Person;)V
#81 = Utf8 java/lang/invoke/LambdaMetafactory
#82 = Utf8 metafactory
#83 = Class #88 // java/lang/invoke/MethodHandles$Lookup
#84 = Utf8 Lookup
#85 = Utf8 InnerClasses
#86 = Utf8 (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;
#87 = Class #89 // java/lang/invoke/MethodHandles
#88 = Utf8 java/lang/invoke/MethodHandles$Lookup
#89 = Utf8 java/lang/invoke/MethodHandles
{
public CompilerTestPackage.LambdaTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LCompilerTestPackage/LambdaTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: invokedynamic #2, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
5: astore_1
6: aload_1
7: new #3 // class CompilerTestPackage/Person
10: dup
11: ldc #4 // String Lambda
13: invokespecial #5 // Method CompilerTestPackage/Person."<init>":(Ljava/lang/String;)V
16: invokeinterface #6, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
21: return
LineNumberTable:
line 8: 0
line 9: 6
line 10: 21
LocalVariableTable:
Start Length Slot Name Signature
0 22 0 args [Ljava/lang/String;
6 16 1 greeter Ljava/util/function/Consumer;
LocalVariableTypeTable:
Start Length Slot Name Signature
6 16 1 greeter Ljava/util/function/Consumer<LCompilerTestPackage/Person;>;
}
SourceFile: "LambdaTest.java"
InnerClasses:
public static final #84= #83 of #87; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #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;
Method arguments:
#41 (Ljava/lang/Object;)V
#42 invokestatic CompilerTestPackage/LambdaTest.lambda$main$0:(LCompilerTestPackage/Person;)V
#43 (LCompilerTestPackage/Person;)V
- 從字節(jié)碼中可以看出,lambda表達(dá)式是通過invokedynamic實(shí)現(xiàn)的碌冶;
lambda表達(dá)式的invokedynamic指令
-
invokedynamic指令有四個(gè)操作數(shù)湿痢,格式為
invokedynamic indexbyte1 indexbyte2 0 0
- 前兩個(gè)操作數(shù)構(gòu)成一個(gè)索引,指向常量池扑庞,關(guān)聯(lián)CONSTANT_InvokeDynamic_info結(jié)構(gòu)譬重;
- 后兩個(gè)操作數(shù)保留,必須為0罐氨;
-
CONSTANT_InvokeDynamic_info 結(jié)構(gòu)定義
CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; // 指向bootstrap_methods的一個(gè)有效索引值臀规,其結(jié)構(gòu)在屬性表的 bootstrap method 結(jié)構(gòu)中 u2 name_and_type_index; }
-
BootstrapMethods屬性結(jié)構(gòu)
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]; }
指令調(diào)用圖
lambda的機(jī)制總結(jié)
編譯時(shí)
- lambda表達(dá)式生產(chǎn)一個(gè)方法,方法實(shí)現(xiàn)了表達(dá)式的代碼邏輯栅隐;
- 編譯生成invokedynamic指令塔嬉,調(diào)用bootstrap方法,由java.lang.invoke.LambdaMetafactory.metafactory()方法實(shí)現(xiàn)租悄;
運(yùn)行時(shí)
- invokedynamic指令調(diào)用metafactory方法谨究,返回一個(gè)callsite,此callsite返回目標(biāo)類型的一個(gè)匿名實(shí)現(xiàn)類(MethodHandles.Lookup caller 的內(nèi)部類)泣棋,此類關(guān)聯(lián)編譯時(shí)產(chǎn)生的方法胶哲;
- lambda表達(dá)式調(diào)用時(shí)會(huì)調(diào)用匿名實(shí)現(xiàn)類關(guān)聯(lián)的方法。
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();
}
metafactory方法的參數(shù)
- caller: 由JVM提供的lookup context
- invokedName: JVM提供的NameAndType
- invokedType: JVM提供的期望的CallSite類型
- samMethodType: 函數(shù)式接口定義的方法的簽名
- implMethod: 編譯時(shí)產(chǎn)生的那個(gè)實(shí)現(xiàn)方法
- instantiatedMethodType: 強(qiáng)制的方法簽名和返回類型潭辈, 一般和samMethodType相同或者是它的一個(gè)特例
查看匿名實(shí)現(xiàn)類
IDEA配置JVM參數(shù)
- Run-->Edit Configurations
- 在VM Options內(nèi)輸入-Djdk.internal.lambda.dumpProxyClasses=<path_to_your_dump_directory>
- 匿名類查看
package CompilerTestPackage;
import CompilerTestPackage.LambdaTest;
import CompilerTestPackage.Person;
import java.lang.invoke.LambdaForm;
import java.util.function.Consumer;
final class LambdaTest$$Lambda$1
implements Consumer {
private LambdaTest$$Lambda$1() {
}
@LambdaForm.Hidden
public void accept(Object object) {
LambdaTest.lambda$main$0((Person)((Person)object));// 導(dǎo)入lambda$main$0方法
}
}
invokedynamic指令學(xué)習(xí)
-
理念
要讓invokedynamic正常運(yùn)行鸯屿,一個(gè)核心的概念就是方法句柄(method handle)。它代表了一個(gè)可以從invokedynamic調(diào)用點(diǎn)進(jìn)行調(diào)用的方法萎胰。這里的基本理念就是每個(gè)invokedynamic指令都會(huì)與一個(gè)特定的方法關(guān)聯(lián)(也就是引導(dǎo)方法或BSM)碾盟。當(dāng)解釋器(interpreter)遇到invokedynamic指令的時(shí)候,BSM會(huì)被調(diào)用技竟。它會(huì)返回一個(gè)對(duì)象(包含了一個(gè)方法句柄)冰肴,這個(gè)對(duì)象表明了調(diào)用點(diǎn)要實(shí)際執(zhí)行哪個(gè)方法。
-
一個(gè)Java方法可以視為由四個(gè)基本內(nèi)容所構(gòu)成:
- 名稱
- 簽名(包含返回類型)
- 定義它的類
- 實(shí)現(xiàn)方法的字節(jié)碼