After understanding some of the basic concepts and applications of Java 8 Lambda, we have a question: what is a lambda expression compiled into? This is an interesting question that involves the specific implementation of the JDK. This article introduces the details of OpenJDK's conversion to lambda expressions, and allows the reader to understand the Java 8 lambda expression background.
Conversion strategies for lambda expressions
Brian Goetz is Oracle's Java language Architect, the lead of the JSR 335 (Lambda Expression) specification, and has written several lambda design articles, one of which is translation of Lambda Expressions. This article describes the Java 8 lambda design considerations and how to implement them.
He mentions that lambda expressions can be implemented in the form of internal classes, method handle, dynamic proxies, and so on, but these methods have advantages and disadvantages. To really implement a lambda expression, you must take into account two goals: one is to not introduce a specific strategy, in order to provide maximum flexibility for future optimizations, and to maintain the stability of the class file format. With the invokedynamic (JSR 292) Introduced in Java 7, these two goals can be well taken into account.
Invokedynamic can support efficient and flexible method invocation without the static type information. Primarily for the growing dynamic type language running on the JVM, such as groovy, JRuby.
Invokedynamic deferred the conversion strategy of the lambda expression to run time, which means that the code we are compiling now will work properly in the event of a future change in the conversion strategy.
At compile time, the compiler will desugar the expression body (lambda body) of the lambda expression into a method in which the parameter list and return type of the method are consistent with the lambda expression, and if there are catch parameters, the parameters of the de-icing method may be more And will generate a call site called invokedynamic. This call site returns an implementation class for the target type of the lambda expression (functional interface) when called. This call site is called the Lambda factory of this lambda expression. The bootstrap method of the Lambda factory is a standard method called the Lambda metafactory.
When the compiler transforms a lambda expression, it can infer the argument type of the expression, the return type, and the exception, which natural signature
we call the method signature of the target type lambda descriptor
, which implements the functional interface of the return object of the Lambda factory, and the code logic of the associated expression , called lambda object
.
Conversion examples
The explanations above are a bit obscure, simply speaking
- Compile-time
- A LAMBDA expression generates a method that implements the code logic of the expression
- Generate invokedynamic directives, call the Bootstrap method, implemented by the Java.lang.invoke.LambdaMetafactory.metafactory method
- Run-time
- The invokedynamic directive calls the Metafactory method. It returns a callsite that returns an anonymous implementation class for the target type, which is associated with the method that is generated at compile time
- A lambda expression invokes a method that is associated with an anonymous implementation class.
The simplest example of a lambda expression:
public class Lambda1 {public static void main (string[] args) {consumer<string> C = S System.out.println (s); C.A Ccept ("Hello Lambda");}}
Use JAVAP to view the generated bytecode javap -c -p -v com/colobu/lambda/chapter5/Lambda1.class
:
[[email protected] bin]# javap-c-p-v com/colobu/lambda/chapter5/lambda1.class classfile/mnt/eclipse/lambda/ Bin/com/colobu/lambda/chapter5/lambda1.class Last modified Nov 6, 2014; Size 1401 bytes MD5 checksum fe2b2d3f039a9ba4209c488a8c4b4ea8 Compiled from ' Lambda1.java ' public class Com.colobu.lambda . Chapter5. LAMBDA1 sourcefile: "Lambda1.java" bootstrapmethods:0: #57 invokestatic java/lang/invoke/lambdametafactory.metafacto Ry: (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: #58 (ljava/lang/object;) V #61 invokestatic com/colobu/lambda/chapter5/lambda1.lambda$0: (Lj ava/lang/string;) v #62 (ljava/lang/string;) v innerclasses:public static Final #68 = #64 of #66;//lookup=cla SS Java/lang/invoke/methodhandles$lookup of Class Java/lang/invoke/methodhandles minor version:0Major version:52 Flags:acc_public, acc_superconstant pool: #1 = Class #2//Com/colobu/lamb da/chapter5/lambda1 #2 = Utf8 com/colobu/lambda/chapter5/lambda1 #3 = Class #4 Java/lang/object #4 = Utf8 java/lang/object #5 = Utf8 <init> #6 = Utf8 () V #7 = Utf8 Code #8 = methodref #3. #9//Java/lang/object. " <init>:() v #9 = Nameandtype #5: #6//"<init>":() v #10 = Utf8 linenumbertabl E #11 = Utf8 localvariabletable #12 = Utf8 this #13 = Utf8 Lcom/colobu/lambda /CHAPTER5/LAMBDA1; #14 = Utf8 Main #15 = Utf8 ([ljava/lang/string;) V #16 = Nameandtype #17: #18// Accept: () Ljava/util/function/consumer; #17 = Utf8 Accept #18 = Utf8 () Ljava/util/function/consumer; #19 = InvoKedynamic #0: #16//#0: Accept: () Ljava/util/function/consumer; #20 = String #21//Hello lambda #21 = Utf8 Hello lambda #22 = interfacemethodref #23. #25//java/util/function/consumer.accept: (ljava/lang/object;) V #23 = Class #24//J Ava/util/function/consumer #24 = Utf8 Java/util/function/consumer #25 = nameandtype #17: #26 Accept: (ljava/lang/object;) v #26 = Utf8 (ljava/lang/object;) v #27 = Utf8 args #28 = UTF 8 [ljava/lang/string; #29 = Utf8 c #30 = Utf8 Ljava/util/function/consumer; #31 = Utf8 localvariabletypetable #32 = Utf8 Ljava/util/function/consumer<ljava/lang/strin g;>; #33 = Utf8 lambda$0 #34 = Utf8 (ljava/lang/string;) V #35 = Fieldref #36. #38 Java/lang/system.out:ljava/io/printstream; #36 = Class #37//Java/lang/system #37 = Utf8 Java/lang/system #38 = Nameandtype #39: #40//Out:ljava/io/printstream; #39 = Utf8 out #40 = Utf8 ljava/io/printstream; #41 = MethodRef #42. #44//JAVA/IO/PRINTSTREAM.PRINTLN: (ljava/lang/string;) V #42 = Class #4 3//Java/io/printstream #43 = Utf8 Java/io/printstream #44 = nameandtype #45: #34 println: (ljava/lang/string;) V #45 = Utf8 println #46 = Utf8 s #47 = Utf8 ljava/lang/string; #48 = Utf8 sourcefile #49 = Utf8 Lambda1.java #50 = Utf8 bootstrapmethods #51 = MethodRef #52. #54//java/lang/invoke/lambdametafactory.metafactory: (ljava/lang/invoke/methodhandles$l Ookup; ljava/lang/string; Ljava/lang/invoke/methodtype; Ljava/lang/invoke/methodtype; Ljava/lang/invoke/methodhandle; Ljava/lang/inVoke/methodtype;) Ljava/lang/invoke/callsite; #52 = Class #53//Java/lang/invoke/lambdametafactory #53 = Utf8 java/lang/invoke/ Lambdametafactory #54 = Nameandtype #55: #56//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; #55 = Utf8 Metafactory #56 = 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; #57 = Methodhandle #6: #51//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; #58 = Methodtype #26//(ljava/lang/object;) V #59 = MethodRef #1. #60//com/colobu/ Lambda/chapter5/lambda1.lambda$0: (ljava/lang/string;) V #60 = Nameandtype #33: #34//lambda$0: (ljava/lang/s Tring;) V #61 = Methodhandle #6: #59//Invokestatic com/colobu/lambda/chapter5/lambda1.lambda$0: (Ljava/lang /string) v #62 = Methodtype #34//(ljava/lang/string;) v #63 = Utf8 innerclasses #64 = Class #65//Java/lang/invoke/methodhandles$lookup #65 = Utf8 java/lang/invoke/m Ethodhandles$lookup #66 = Class #67//java/lang/invoke/methodhandles #67 = Utf8 Java/lang/invoke/methodhandles #68 = Utf8 lookup{public com.colobu.lambda.chapter5.Lambda1 (); Flags:acc_public code:stack=1, Locals=1, args_size=1 0:aload_0 1:invokespecial #8 Method java/lang/object. " <init> ":() V 4:return linenumbertable:line 7:0 Localvariabletable:start Length Slot Name Signature 0 5 0 this lcom/colobu/lambda/chapter5/lambda1; public static void Main (java.lang.string[]); Flags:acc_public, Acc_static code:stack=2, locals=2, args_size=1 0:invokedynamic #19, 0// Invokedynamic #0: Accept: () Ljava/util/function/consumer; 5:astore_1 6:aload_1 7:LDC #20//String Hello Lambda 9: Invokeinterface #22, 2//Interfacemethod java/util/function/consumer.accept: (ljava/lang/object;) V 14: Return linenumbertable:line 10:0 line 11:6 line 12:14 localvariabletable: Start Length Slot Name Signature 0 0 args [ljava/lang/string; 6 9 1 C Ljava/util/function/consumer; Localvariabletypetable:start Length Slot Name Signature 6 9 1 C ljava/util/functio n/consumer<ljava/lang/string;>; private static void Lambda$0 (java.lang.String); Flags:acc_private, Acc_static, Acc_synthetic code:stack=2, Locals=1, args_size=1 0:getstatic #35 Field Java/lang/system.out:ljava/io/printstream; 3:aload_0 4:invokevirtual #41//Method java/io/printstream.println: (ljava/lang/string;) V 7:return linenumbertable:line 10:0 localvariabletable:start Length Slot Na Me Signature 0 8 0 s ljava/lang/string;}
As you can see, the lambda expression body is generated as a lambda$0
method called. Look at the bytecode to know that it calls SYSTEM.OUT.PRINTLN output incoming parameters.
The original lambda expression produces a single line invokedynamic #19, 0
. It will call the bootstrap
method.
0: #57 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: #58 (ljava/lang/object;) V #61 invokestatic com/colobu/lambda/chapter5/lambda1.lambda$0: ( ljava/lang/string;) v #62 (ljava/lang/string;) v
If the lambda expression is written Consumer<String> c = (Consumer<String> & Serializable)s -> System.out.println(s);
, the Bootstrapmethods byte code is
Bootstrapmethods: 0: #108 invokestatic java/lang/invoke/lambdametafactory.altmetafactory: (Ljava/lang/invoke/ Methodhandles$lookup; ljava/lang/string; Ljava/lang/invoke/methodtype; [Ljava/lang/object;) Ljava/lang/invoke/callsite; Method arguments: #109 (ljava/lang/object;) V #112 invokestatic com/colobu/lambda/chapter5/lambda1.lambda$0 :(ljava/lang/string;) v #113 (ljava/lang/string;) v #114 1
It is called LambdaMetafactory.altMetafactory
, and the method above calls is different. #114 1
means to implement an Serializable
interface.
If the lambda expression is written as ", the Bootstrapmethods bytecode is
Bootstrapmethods: 0: #57 invokestatic java/lang/invoke/lambdametafactory.altmetafactory: (Ljava/lang/invoke/ Methodhandles$lookup; ljava/lang/string; Ljava/lang/invoke/methodtype; [Ljava/lang/object;) Ljava/lang/invoke/callsite; Method arguments: #58 (ljava/lang/object;) V #61 invokestatic com/colobu/lambda/chapter5/lambda1.lambda$0: ( ljava/lang/string;) v #62 (ljava/lang/string;) v #63 2 #64 1 #65 com/colobu/lambda/chapter5/abc
#63 2
means to implement additional interfaces. #64 1
means that the number of additional interfaces to implement is 1.
Byte code directive meaning can refer to this article: Java bytecode instruction listings.
As you can see, the specific transformations of the lambda expression are implemented through Java.lang.invoke.LambdaMetafactory.metafactory, and the static parameters vary according to the lambda expression and the target type.
Lambdametafactory.metafactory
Now we can focus on LambdaMetafactory.metafactory
the following implementations.
public static CallSite metafactory (methodhandles.lookup caller, String Invokedn Ame, Methodtype Invokedtype, Methodtype Sammet Hodtype, Methodhandle Implmethod, Methodtype i Nstantiatedmethodtype) throws Lambdaconversionexception {return value type abstractvalidatinglambdametafactory MF; MF = new Innerclasslambdametafactory (caller, Invokedtype, Invokedname, S Ammethodtype, Implmethod, Instantiatedmethodtype, False, Empty_class_array, Empty_mt_array); Mf.validatemetafactoryargs (); return Mf.buildcallsite (); }
is actually InnerClassLambdaMetafactory
generated by the buildCallSite
. The parameters/ validateMetafactoryArgs
return value types of the method validation target type (SAM) method are called consistent before the build is generated.
metaFactory
Parameters of the method:
- Caller: Lookup context provided by the JVM
- Nameandtype provided by INVOKEDNAME:JVM
- INVOKEDTYPE:JVM provided by the desired callsite type
- Sammethodtype: Signature of a method defined by a functional interface
- Implmethod: The implementation method generated at compile time
- Instantiatedmethodtype: Mandatory method signature and return type, usually same as sammethodtype or a special case of it
The above code is basically InnerClassLambdaMetafactory.buildCallSite
the wrapper, below to see the implementation of this method:
CallSite Buildcallsite () throws Lambdaconversionexception { final class<?> innerclass = Spininnerclass (); if (invokedtype.parametercount () = = 0) {...//Call the constructor to initialize an instance of the Sam return new Constantcallsite ( Methodhandles.constant (Sambase, inst)); } else { unsafe.ensureclassinitialized (innerclass); return new Constantcallsite ( MethodHandles.Lookup.IMPL_LOOKUP findstatic (Innerclass, Name_factory, Invokedtype)); } }
Where the spinInnerClass
calling asm
framework dynamically produces the SAM implementation class, this method of implementing the class will invoke the implementation method generated at compile time.
You can add parameters when compiling -Djdk.internal.lambda.dumpProxyClasses
, so that when compiling, the spinInnerClass
class generated by the runtime is generated automatically.
You can access the OPENJDK bug system to learn about this feature. JDK-8023524
Duplicate lambda expression
In the following code, a recurring call to a lambda expression in a loop generates only the same lambda object because there is only one invokedynamic
instruction.
for (int i = 0; i<100; i++) {consumer<string> C = S System.out.println (s); System.out.println (C.hashcode ());}
However, the following code generates two lambda objects because it generates two invokedynamic
instructions.
Consumer<string> C = S-System.out.println (s); System.out.println (C.hashcode ()); consumer<string> C2 = S, System.out.println (s); System.out.println (C2.hashcode ());
The generated class name
Since Lambdametafactory uses the asm
framework to generate an anonymous class, the class name of this class has a regular pattern.
Consumer<string> C = S-System.out.println (s); System.out.println (C.getclass (). GetName ()); System.out.println (C.getclass (). Getsimplename ()); System.out.println (C.getclass (). Getcanonicalname ());
The output results are as follows:
com.colobu.lambda.chapter5.lambda3$ $Lambda $1/640070680lambda3$ $Lambda $1/ 640070680com.colobu.lambda.chapter5.lambda3$ $Lambda $1/640070680
Class name format such as < package name >.< class name >$ $Lambda $/.
Number is generated by a counter counter.incrementandget ().
/<NN>
the number in the suffix is a hash value, which is the hash value of the class object c.getClass().hashCode()
.
Klass::external_name()
generated in.
sprintf (Hash_buf, "/" Uintx_format, (uintx) hash);
Call the generated method directly
As mentioned above, the lambda expression realizes that the compiler generates a method with a name format such as Lambda$XXX
.
Since it is a real method in the class, we can call it directly. Of course, you write the compiler directly in the code lambda$0()
, because the lambda expression body has not yet been extracted as a method.
But in the run we can call it in a reflection way. The following example calls this method in two ways using launch and Methodhandle.
public static void Main (string[] args) throws Throwable {consumer<string> C = S-System.out.println (s); Method m = Lambda4.class.getDeclaredMethod ("lambda$0", String.class); M.invoke (null, "Hello reflect"); Methodhandle MH = Methodhandles.lookup (). Findstatic (Lambda4.class, "lambda$0", Methodtype.methodtype (Void.class, String.class)); Mh.invoke ("Hello Methodhandle");}
The captured variable is equivalent to ' final '
We know that when calling an external parameter in an anonymous class, the parameter must be declared as final
.
A variable in the context can also be referenced in a lambda body, but the variable may not be declared final
, but must be equivalent to final
.
In the following example, the variable Capturedv final
is equivalent to, and is not re-assigned in context.
public class Lambda5 {String greeting = "Hello";p ublic static void Main (string[] args) throws Throwable {Lambda5 Capturedv = new Lambda5 (); Consumer<string> C = S-System.out.println (capturedv.greeting + "" + s); C.accept ("Captured variable");//captu Redv = null; Local variable Capturedv defined in a enclosing scope must be final or effectively final//capturedv.greeting = "HI";}}
If capturedV = null;
there is an error in the anti-comment compilation, the CAPTUREDV is changed in the context.
However, if the anti-annotation capturedV.greeting = "hi";
is not a problem, because the CAPTUREDV is not re-assigned, only the object that it points to has a change in properties.
Method reference
public static void Main (string[] args) throws Throwable {consumer<string> c = System.out::p rintln;c.accept (" Hello ");}
This code does not produce a new method like "Lambda$0″". Because Lambdametafactory will use this method of reference directly.
Bootstrapmethods: 0: #51 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: #52 (ljava/lang/object;) V #59 invokevirtual java/io/printstream.println: (ljava/lang/ String;) v #60 (ljava/lang/string;) v
#59
Indicates the implementation method is System.out::p rintln
Java 8 LAMBDA Disclosure