Java 8 dynamic typing language Lambda expression implementation principle analysis
- 2020-06-03 06:34:47
- OfStack
Java 8 supports dynamic languages, see the cool Lambda expressions, and Java 1 treats itself as a statically typed language, see the goal of Java virtual machines that can support dynamic languages.
import java.util.function.Consumer;
public class Lambda {
public static void main(String[] args) {
Consumer<String> c = s -> System.out.println(s);
c.accept("hello lambda!");
}
}
When you first see this expression, it feels like java handles it in a way that belongs to an internal anonymous class
public class Lambda {
static {
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
}
public static void main(String[] args) {
Consumer<String> c = new Consumer<String>(){
@Override
public void accept(String s) {
System.out.println(s);
}
};
c.accept("hello lambda");
}
}
The result of the compilation should be Lambda. class, Lambda$1.class guesses that in support of dynamic language java is unchanged, generating our common way at the last compilation.
But that's not the case, it's just 1 Lambda.class
Decompilation, what's the truth?
javap -v -p Lambda.class
Note that the -p parameter -p displays all methods, without the default method of private that will not be decompiled
public Lambda();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #21 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LLambda;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
5: astore_1
6: aload_1
7: ldc #31 // String hello lambda
9: invokeinterface #33, 2 // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
14: return
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 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/function/Consumer<Ljava/lang/String;>;
private static void lambda$0(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #46 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 s Ljava/lang/String;
}
SourceFile: "Lambda.java"
BootstrapMethods:
0: #66 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:
#67 (Ljava/lang/Object;)V
#70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
#71 (Ljava/lang/String;)V
InnerClasses:
public static final #77= #73 of #75; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
Here we find a few differences from the common java, which are not posted in the article due to too many constant definitions
1. Invokedynamic instructions
Java calls the function of the four commands (invokevirtual invokespecial invokestatic, invokeinterface), usually method of symbols in a static reference types can produce language compilation, dynamically typed languages only at runtime to determine the receiver type, the semantic change four instructions for java version has a great influence, so in JSR 292 "Supporting Dynamically Typed Languages on the Java Platform" add a new instruction
Invokedynamic
0: invokedynamic #30, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
# 30 represents a constant # 30 is the back of the annotation InvokeDynamic # 0: accept: () Ljava/util/function/Consumer;
0 is a placeholder symbol and is currently useless
2. BootstrapMethods
Every 1 invokedynamic instance of instructions is called a dynamic invocation points (dynamic call site), the dynamic invocation points at the beginning is not link state (unlinked: not specified the call point to invoke methods), the dynamic invocation points depend on guiding method to link to a specific method. Guide method is generated by the compiler, at run time when JVM first met invokedynamic instructions, Will call the guiding method to invokedynamic instructions specified by name, method name, the method signature) and specific code execution method (target) link, guide the method return value calls the permanent decision behavior. Guide the method return value type is java lang. invoke. CallSite, 1 invokedynamic instructions associated 1 CallSite, will all the delegate to the current target CallSite (MethodHandle)
InvokeDynamic #0 is where BootstrapMethods stands for #0
0: #66 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:
#67 (Ljava/lang/Object;)V
#70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
#71 (Ljava/lang/String;)V
We see that the method LambdaMetaFactory.metafactory is called
Parameters:
LambdaMetafactory.metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType) has six parameters, described in the following order
MethodHandles. Lookup caller: represents the lookup context and the caller's access rights. When using the invokedynamic directive, JVM automatically populates this parameter
String invokedName: The name of the method to be implemented. When using invokedynamic, JVM fills for us automatically (from the constant pool InvokeDynamic.NameAndType.Name). In this case, JVM fills for us as "apply", the name of the method Consumer.accept.
3. MethodType invokedType: Call some expectations, the type of method parameters and return values of type signature (method). When using invokedynamic commands, automatic JVM will automatically fill the parameters (filling content from InvokeDynamic constant pool. NameAndType. Type), parameters for String here, the return value type for Consumer, said this call some of the parameters of the target method as String, then invokedynamic execution will return after 1 namely Consumer instance.
4. MethodType samMethodType: the type of interface method the function object will implement, here run with the value (Object)Object, Consumer.accept method type (generic information erased).#67 (Ljava/lang/Object;) V
5. MethodHandle implMethod: 1 direct method handle (DirectMethodHandle), describing the specific implementation method to be executed on invocation (including appropriate parameter adaptation, return type adaptation, and the parameters captured attached to the invocation parameters), here #70 invokestatic Lambda. lambda$0:(Ljava/lang/String;) A method handle to the V method.
6. MethodType instantiatedMethodType: The method type after the function interface method replaces the generic type with the specific type, usually the same as samMethodType 1.
For example, the function interface method is defined as void accept(T t) T is the generic identifier, at this time the method type is (Object)Void, T has been determined at compile time, that is, T is replaced by String, samMethodType is (Object)Void, and instantiatedMethodType is (String)Void.
Parameters 4, 5, 6 are from the class file. For example, in the above boot method bytecode, the three parameters after Method arguments are the parameters to be applied to 4, 5, and 6.
Method arguments:
#67 (Ljava/lang/Object;)V
#70 invokestatic Lambda.lambda$0:(Ljava/lang/String;)V
#71 (Ljava/lang/String;)V
Let's look at the implementation code in metafactory's method
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();
}
In the buildCallSite function
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
The function spinInnerClass builds this inner class, which generates an inner class like Lambda$$Lambda$1/716157500. This class is built at run time and is not stored on disk. If you want to see the built class, you can set the environment parameters
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
This inner class will be generated on the path you specified. The current run path
3. Static class
Java generates lambda$0 static private class when compiling the expression, which implements the method block system. out. println(s) in the expression.
private static void lambda$0(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #46 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 s Ljava/lang/String;
Of course in step 1 by setting the jdk. internal. lambda. dumpProxyClasses generated in Lambda $$Lambda $1. class
public class Lambda {
static {
System.setProperty("jdk.internal.lambda.dumpProxyClasses", ".");
}
public static void main(String[] args) {
Consumer<String> c = new Consumer<String>(){
@Override
public void accept(String s) {
System.out.println(s);
}
};
c.accept("hello lambda");
}
}
0
The Lambda. lambda$0 static function, which is the function block in the expression, is called
conclusion
This completes the implementation of Lambda expression, using invokedynamic instruction, the runtime call ES247en.metafactory dynamic generation of the inner class, the implementation of the interface, the inner class call method block is not dynamically generated, just in the original class has been compiled to generate a static method, the inner class only need to call the static method