简体   繁体   中英

Where does the extra parameter in a compiled lambda function come from?

I'm trying to figure out exactly how lambdas and closures work in the JVM. To that end, I've tried compiling this simple test case:

import java.util.function.*;

class Adder {
  static Function<Float, Float> makeAdder(Float a) {
    return b -> a + b;
  }

  public static void main(String[] args) {
    Function<Float, Float> f = makeAdder(1.23f);
    System.out.println(f.apply(4.56f));
  }
}

Disassembling the resulting byte code is interesting:

  static java.util.function.Function<java.lang.Float, java.lang.Float> makeAdder(java.lang.Float);
    descriptor: (Ljava/lang/Float;)Ljava/util/function/Function;
    flags: (0x0008) ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #7,  0              // InvokeDynamic #0:apply:(Ljava/lang/Float;)Ljava/util/function/Function;
         6: areturn
      LineNumberTable:
        line 4: 0
    Signature: #48                          // (Ljava/lang/Float;)Ljava/util/function/Function<Ljava/lang/Float;Ljava/lang/Float;>;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: ldc           #11                 // float 1.23f
         2: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
         5: invokestatic  #18                 // Method makeAdder:(Ljava/lang/Float;)Ljava/util/function/Function;
         8: astore_1
         9: getstatic     #23                 // Field java/lang/System.out:Ljava/io/PrintStream;
        12: aload_1
        13: ldc           #29                 // float 4.56f
        15: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
        18: invokeinterface #30,  2           // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        23: invokevirtual #35                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        26: return
      LineNumberTable:
        line 9: 0
        line 10: 9
        line 11: 26

  private static java.lang.Float lambda$makeAdder$0(java.lang.Float, java.lang.Float);
    descriptor: (Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: invokevirtual #41                 // Method java/lang/Float.floatValue:()F
         4: aload_1
         5: invokevirtual #41                 // Method java/lang/Float.floatValue:()F
         8: fadd
         9: invokestatic  #12                 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float;
        12: areturn
      LineNumberTable:
        line 4: 0
        

Some of the above is clear, some less so. The part I'm most puzzled about right now is the implementation of the lambda function, lambda$makeAdder$0(java.lang.Float, java.lang.Float) . That signature suggests the lambda has two parameters even though it was declared with just one.

Well, it's obvious what the extra one is for; it's for the value of a that was bound into the closure. So at one level, that answers the question of how Java closures are supplied with the values of bound variables: they are prepended to the parameter list.

But then, how does the ultimate caller know about this? The disassembled code for main looks isomorphic to the source code, ie completely innocent of knowledge about how closures are implemented. It seems to be supplying one argument to makeAdder , then the second argument to the lambda. In other words, supplying just one argument to the lambda.

How is the first argument also supplied to the lambda?

Does it anything to do with the final BootstrapMethods section of the disassembled code?

BootstrapMethods:
  0: #56 REF_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:
      #63 (Ljava/lang/Object;)Ljava/lang/Object;
      #64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
      #67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
  public static final #74= #70 of #72;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

The relevant aspect is in the output of javap but you didn't include it in your question. At the very end of the javap output: (make sure to run with javap -c -v !)

SourceFile: "Test.java"
BootstrapMethods:
  0: #56 REF_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:
      #63 (Ljava/lang/Object;)Ljava/lang/Object;
      #64 REF_invokeStatic Adder.lambda$makeAdder$0:(Ljava/lang/Float;Ljava/lang/Float;)Ljava/lang/Float;
      #67 (Ljava/lang/Float;)Ljava/lang/Float;
InnerClasses:
  public static final #74= #70 of #72;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

This... doesn't really come across as useful, but it is the underlying mechanism that makes all this work.

The relevant call is the invokedynamic bytecode instruction. invokedynamic is, well, dynamic: The first time invokedynamic is encountered the JVM will take a slow path and executes some code that can arbitrarily decide what this invocation is actually going to end up looking like. Any further executions of that same invokedynamic call no longer do that - they 'memoize'. So, an invokedynamic call can result in any kind of actual method invocation you want (here, it'll end up being a partial application of that lambda$makeAdder$0(float, float) , of course), and is nevertheless fast.

javap 's output isn't all that enlightening. A lot of the crucial moving parts of it all are actually in the java.lang.invoke.LambdaMetaFactory class which is a real class in the JVM whose source is part of the core JDK and thus open .

This baeldung tutorial on invokedynamic is probably something you want to go through top to bottom if you are interested in fully understanding how this works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM