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


Related articles: